/*******************************************************************************
 * Copyright (c) 2026, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#include <iostream>
#include <cstring>
#include <ctime>
#include <random>
#include <algorithm>
#include <memory>
#include <chrono>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <numeric>

#include <netplus/socket.h>
#include <netplus/connection.h>
#include <netplus/eventapi.h>
#include <netplus/crypto/aes.h>
#include <netplus/crypto/sha.h>

#include "krb5.h"
#include "authdb.h"
#include "backend.h"
#include "user.h"
#include "client.h"
#include "service.h"
#include "domain.h"
#include "types.h"
#include "exception.h"
#include "hash/sha512.h"


#if defined(_WIN32) || defined(_WIN64)
static inline struct tm *portable_gmtime_r(const time_t *t, struct tm *out) {
    return (_gmtime64_s(out, t) == 0) ? out : nullptr;
}

static inline time_t portable_timegm(struct tm *tmv) {
    return _mkgmtime(tmv);
}
#else
static inline struct tm *portable_gmtime_r(const time_t *t, struct tm *out) {
    return gmtime_r(t, out);
}

static inline time_t portable_timegm(struct tm *tmv) {
    return timegm(tmv);
}
#endif

namespace authdb {

/******************************************************************************
 * Helper: to-upper-case a string (realm names are upper-cased domain names)
 *****************************************************************************/
namespace {
    std::string toUpperRealm(const std::string &name) {
        std::string r = name;
        std::transform(r.begin(), r.end(), r.begin(),
                       [](unsigned char c){ return std::toupper(c); });
        return r;
    }

    /* Hex-decode a string of hex characters into raw bytes */
    std::vector<uint8_t> hexDecode(const std::string &hex) {
        std::vector<uint8_t> out;
        out.reserve(hex.size() / 2);
        for (size_t i = 0; i + 1 < hex.size(); i += 2) {
            unsigned int byte = 0;
            auto nibble = [](char c) -> int {
                if (c >= '0' && c <= '9') return c - '0';
                if (c >= 'a' && c <= 'f') return 10 + c - 'a';
                if (c >= 'A' && c <= 'F') return 10 + c - 'A';
                return -1;
            };
            int hi = nibble(hex[i]);
            int lo = nibble(hex[i + 1]);
            if (hi < 0 || lo < 0) break;
            byte = (static_cast<unsigned>(hi) << 4) | static_cast<unsigned>(lo);
            out.push_back(static_cast<uint8_t>(byte));
        }
        return out;
    }

    /* GCD for n-fold */
    size_t gcd(size_t a, size_t b) {
        while (b) { size_t t = b; b = a % b; a = t; }
        return a;
    }
}

/******************************************************************************
 * ASN.1 DER Encoder / Decoder Implementation
 *
 * Minimal but complete for Kerberos 5 (RFC 4120) wire compatibility with
 * MIT Kerberos and Heimdal.
 *****************************************************************************/

/* ---- Encoding ---- */

void Asn1Der::encodeLength(std::vector<uint8_t> &buf, size_t len) {
    if (len < 0x80) {
        buf.push_back(static_cast<uint8_t>(len));
    } else if (len < 0x100) {
        buf.push_back(0x81);
        buf.push_back(static_cast<uint8_t>(len));
    } else if (len < 0x10000) {
        buf.push_back(0x82);
        buf.push_back(static_cast<uint8_t>((len >> 8) & 0xFF));
        buf.push_back(static_cast<uint8_t>(len & 0xFF));
    } else if (len < 0x1000000) {
        buf.push_back(0x83);
        buf.push_back(static_cast<uint8_t>((len >> 16) & 0xFF));
        buf.push_back(static_cast<uint8_t>((len >> 8) & 0xFF));
        buf.push_back(static_cast<uint8_t>(len & 0xFF));
    } else {
        buf.push_back(0x84);
        buf.push_back(static_cast<uint8_t>((len >> 24) & 0xFF));
        buf.push_back(static_cast<uint8_t>((len >> 16) & 0xFF));
        buf.push_back(static_cast<uint8_t>((len >> 8) & 0xFF));
        buf.push_back(static_cast<uint8_t>(len & 0xFF));
    }
}

void Asn1Der::encodeTLV(std::vector<uint8_t> &buf, uint8_t tag,
                         const std::vector<uint8_t> &value) {
    buf.push_back(tag);
    encodeLength(buf, value.size());
    buf.insert(buf.end(), value.begin(), value.end());
}

void Asn1Der::encodeContextTag(std::vector<uint8_t> &buf, uint8_t tagnum,
                                const std::vector<uint8_t> &value) {
    uint8_t tag = CLASS_CONTEXT | CONSTRUCTED | (tagnum & 0x1F);
    encodeTLV(buf, tag, value);
}

std::vector<uint8_t> Asn1Der::encodeInteger(int32_t val) {
    std::vector<uint8_t> result;
    std::vector<uint8_t> bytes;

    if (val >= 0) {
        if (val == 0) {
            bytes.push_back(0);
        } else {
            uint32_t uv = static_cast<uint32_t>(val);
            while (uv > 0) {
                bytes.insert(bytes.begin(), static_cast<uint8_t>(uv & 0xFF));
                uv >>= 8;
            }
            /* Add leading zero if high bit set */
            if (bytes[0] & 0x80)
                bytes.insert(bytes.begin(), 0);
        }
    } else {
        int32_t sv = val;
        int nbytes = 0;
        int32_t test = sv;
        do {
            test >>= 8;
            nbytes++;
        } while (test != -1 && test != 0 && nbytes < 4);
        if (nbytes < 4 && ((sv >> (nbytes * 8 - 1)) & 1) == 0)
            nbytes++;
        for (int i = nbytes - 1; i >= 0; --i)
            bytes.push_back(static_cast<uint8_t>((sv >> (i * 8)) & 0xFF));
    }

    result.push_back(TAG_INTEGER);
    encodeLength(result, bytes.size());
    result.insert(result.end(), bytes.begin(), bytes.end());
    return result;
}

std::vector<uint8_t> Asn1Der::encodeUInteger(uint32_t val) {
    std::vector<uint8_t> result;
    std::vector<uint8_t> bytes;

    if (val == 0) {
        bytes.push_back(0);
    } else {
        uint32_t uv = val;
        while (uv > 0) {
            bytes.insert(bytes.begin(), static_cast<uint8_t>(uv & 0xFF));
            uv >>= 8;
        }
        if (bytes[0] & 0x80)
            bytes.insert(bytes.begin(), 0);
    }

    result.push_back(TAG_INTEGER);
    encodeLength(result, bytes.size());
    result.insert(result.end(), bytes.begin(), bytes.end());
    return result;
}

std::vector<uint8_t> Asn1Der::encodeOctetString(const std::vector<uint8_t> &data) {
    std::vector<uint8_t> result;
    encodeTLV(result, TAG_OCTET_STRING, data);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeGeneralString(const std::string &s) {
    std::vector<uint8_t> result;
    std::vector<uint8_t> sdata(s.begin(), s.end());
    encodeTLV(result, TAG_GENERAL_STRING, sdata);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeGeneralizedTime(time_t t) {
    struct tm utc;
    portable_gmtime_r(&t, &utc);
    char buf[32];
    snprintf(buf, sizeof(buf), "%04d%02d%02d%02d%02d%02dZ",
             utc.tm_year + 1900, utc.tm_mon + 1, utc.tm_mday,
             utc.tm_hour, utc.tm_min, utc.tm_sec);
    std::string ts(buf);
    std::vector<uint8_t> result;
    std::vector<uint8_t> tsdata(ts.begin(), ts.end());
    encodeTLV(result, TAG_GENERALIZED_TIME, tsdata);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeBitString(uint32_t flags) {
    /* Kerberos flags: 4 bytes + 1 byte unused-bits count = 5 bytes value */
    std::vector<uint8_t> result;
    std::vector<uint8_t> value;
    value.push_back(0); /* no unused bits */
    value.push_back(static_cast<uint8_t>((flags >> 24) & 0xFF));
    value.push_back(static_cast<uint8_t>((flags >> 16) & 0xFF));
    value.push_back(static_cast<uint8_t>((flags >> 8) & 0xFF));
    value.push_back(static_cast<uint8_t>(flags & 0xFF));
    encodeTLV(result, TAG_BIT_STRING, value);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeSequence(const std::vector<uint8_t> &contents) {
    std::vector<uint8_t> result;
    encodeTLV(result, TAG_SEQUENCE, contents);
    return result;
}

std::vector<uint8_t> Asn1Der::encodePrincipalName(const Krb5Principal &princ) {
    /* PrincipalName ::= SEQUENCE {
     *   name-type [0] Int32,
     *   name-string [1] SEQUENCE OF GeneralString
     * } */
    std::vector<uint8_t> seq;

    /* [0] name-type */
    auto nameType = encodeInteger(static_cast<int32_t>(princ.type));
    encodeContextTag(seq, 0, nameType);

    /* [1] name-string: SEQUENCE OF GeneralString */
    std::vector<uint8_t> nameSeq;
    auto components = princ.nameComponents();
    for (const auto &comp : components) {
        auto gs = encodeGeneralString(comp);
        nameSeq.insert(nameSeq.end(), gs.begin(), gs.end());
    }
    auto nameString = encodeSequence(nameSeq);
    encodeContextTag(seq, 1, nameString);

    return encodeSequence(seq);
}

std::vector<uint8_t> Asn1Der::encodeEncryptedData(Krb5EncType etype, uint32_t kvno,
                                                    const std::vector<uint8_t> &cipher) {
    /* EncryptedData ::= SEQUENCE {
     *   etype [0] Int32,
     *   kvno  [1] UInt32 OPTIONAL,
     *   cipher [2] OCTET STRING
     * } */
    std::vector<uint8_t> seq;

    auto et = encodeInteger(static_cast<int32_t>(etype));
    encodeContextTag(seq, 0, et);

    if (kvno > 0) {
        auto kv = encodeUInteger(kvno);
        encodeContextTag(seq, 1, kv);
    }

    auto ci = encodeOctetString(cipher);
    encodeContextTag(seq, 2, ci);

    return encodeSequence(seq);
}

std::vector<uint8_t> Asn1Der::encodeEncryptionKey(const Krb5SessionKey &key) {
    /* EncryptionKey ::= SEQUENCE {
     *   keytype [0] Int32,
     *   keyvalue [1] OCTET STRING
     * } */
    std::vector<uint8_t> seq;

    auto kt = encodeInteger(static_cast<int32_t>(key.enctype));
    encodeContextTag(seq, 0, kt);

    auto kv = encodeOctetString(key.keydata);
    encodeContextTag(seq, 1, kv);

    return encodeSequence(seq);
}

std::vector<uint8_t> Asn1Der::encodeTicket(const Krb5Ticket &ticket) {
    /* Ticket ::= [APPLICATION 1] SEQUENCE {
     *   tkt-vno [0] INTEGER,
     *   realm   [1] Realm (GeneralString),
     *   sname   [2] PrincipalName,
     *   enc-part [3] EncryptedData
     * } */
    std::vector<uint8_t> seq;

    auto vno = encodeInteger(ticket.tkt_vno);
    encodeContextTag(seq, 0, vno);

    auto realm = encodeGeneralString(ticket.sname.realm);
    encodeContextTag(seq, 1, realm);

    auto sname = encodePrincipalName(ticket.sname);
    encodeContextTag(seq, 2, sname);

    auto encPart = encodeEncryptedData(ticket.enc_etype, ticket.enc_kvno, ticket.enc_part);
    encodeContextTag(seq, 3, encPart);

    auto seqEnc = encodeSequence(seq);

    /* Wrap in APPLICATION 1 tag */
    std::vector<uint8_t> result;
    encodeTLV(result, APP_TICKET, seqEnc);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeKdcReq(const Krb5KdcReq &req) {
    /* KDC-REQ ::= SEQUENCE {
     *   pvno     [1] INTEGER,
     *   msg-type [2] INTEGER,
     *   padata   [3] SEQUENCE OF PA-DATA OPTIONAL,
     *   req-body [4] KDC-REQ-BODY
     * } */
    std::vector<uint8_t> seq;

    auto pvno = encodeInteger(req.pvno);
    encodeContextTag(seq, 1, pvno);

    auto msgType = encodeInteger(static_cast<int32_t>(req.msg_type));
    encodeContextTag(seq, 2, msgType);

    /* PA-DATA */
    if (!req.padata.empty()) {
        /* padata is already a SEQUENCE OF PA-DATA encoded */
        encodeContextTag(seq, 3, req.padata);
    }

    /* KDC-REQ-BODY */
    std::vector<uint8_t> body;
    {
        std::vector<uint8_t> bodySeq;

        /* [0] kdc-options (BIT STRING) */
        auto opts = encodeBitString(req.kdc_options);
        encodeContextTag(bodySeq, 0, opts);

        /* [1] cname (PrincipalName) - only in AS-REQ */
        if (req.msg_type == KRB_AS_REQ && !req.cname.name.empty()) {
            auto cname = encodePrincipalName(req.cname);
            encodeContextTag(bodySeq, 1, cname);
        }

        /* [2] realm (GeneralString) */
        auto realm = encodeGeneralString(req.sname.realm);
        encodeContextTag(bodySeq, 2, realm);

        /* [3] sname (PrincipalName) */
        if (!req.sname.name.empty()) {
            auto sname = encodePrincipalName(req.sname);
            encodeContextTag(bodySeq, 3, sname);
        }

        /* [4] from (KerberosTime) OPTIONAL */
        if (req.from > 0) {
            auto from = encodeGeneralizedTime(req.from);
            encodeContextTag(bodySeq, 4, from);
        }

        /* [5] till (KerberosTime) */
        auto till = encodeGeneralizedTime(req.till);
        encodeContextTag(bodySeq, 5, till);

        /* [6] rtime (KerberosTime) OPTIONAL */
        if (req.rtime > 0) {
            auto rtime = encodeGeneralizedTime(req.rtime);
            encodeContextTag(bodySeq, 6, rtime);
        }

        /* [7] nonce (UInt32) */
        auto nonce = encodeUInteger(req.nonce);
        encodeContextTag(bodySeq, 7, nonce);

        /* [8] etype (SEQUENCE OF Int32) */
        {
            std::vector<uint8_t> etypeSeq;
            for (auto et : req.etypes) {
                auto etEnc = encodeInteger(static_cast<int32_t>(et));
                etypeSeq.insert(etypeSeq.end(), etEnc.begin(), etEnc.end());
            }
            auto etypeList = encodeSequence(etypeSeq);
            encodeContextTag(bodySeq, 8, etypeList);
        }

        body = encodeSequence(bodySeq);
    }
    encodeContextTag(seq, 4, body);

    auto kdcReq = encodeSequence(seq);

    /* Wrap in APPLICATION tag */
    uint8_t appTag = (req.msg_type == KRB_AS_REQ) ? APP_AS_REQ : APP_TGS_REQ;
    std::vector<uint8_t> result;
    encodeTLV(result, appTag, kdcReq);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeKdcRep(const Krb5KdcRep &rep) {
    /* KDC-REP ::= SEQUENCE {
     *   pvno     [0] INTEGER,
     *   msg-type [1] INTEGER,
     *   padata   [2] SEQUENCE OF PA-DATA OPTIONAL,
     *   crealm   [3] Realm,
     *   cname    [4] PrincipalName,
     *   ticket   [5] Ticket,
     *   enc-part [6] EncryptedData
     * } */
    std::vector<uint8_t> seq;

    auto pvno = encodeInteger(rep.pvno);
    encodeContextTag(seq, 0, pvno);

    auto msgType = encodeInteger(static_cast<int32_t>(rep.msg_type));
    encodeContextTag(seq, 1, msgType);

    /* [2] padata OPTIONAL - omitted for now */

    auto crealm = encodeGeneralString(rep.cname.realm);
    encodeContextTag(seq, 3, crealm);

    auto cname = encodePrincipalName(rep.cname);
    encodeContextTag(seq, 4, cname);

    auto ticket = encodeTicket(rep.ticket);
    encodeContextTag(seq, 5, ticket);

    auto encPart = encodeEncryptedData(rep.enc_etype, rep.enc_kvno, rep.enc_part);
    encodeContextTag(seq, 6, encPart);

    auto kdcRep = encodeSequence(seq);

    /* Wrap in APPLICATION tag */
    uint8_t appTag = (rep.msg_type == KRB_AS_REP) ? APP_AS_REP : APP_TGS_REP;
    std::vector<uint8_t> result;
    encodeTLV(result, appTag, kdcRep);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeEncTicketPart(const Krb5EncTicketPart &etp) {
    /* EncTicketPart ::= [APPLICATION 3] SEQUENCE {
     *   flags          [0] TicketFlags,
     *   key            [1] EncryptionKey,
     *   crealm         [2] Realm,
     *   cname          [3] PrincipalName,
     *   transited      [4] TransitedEncoding,
     *   authtime       [5] KerberosTime,
     *   starttime      [6] KerberosTime OPTIONAL,
     *   endtime        [7] KerberosTime,
     *   renew-till     [8] KerberosTime OPTIONAL,
     * } */
    std::vector<uint8_t> seq;

    auto flags = encodeBitString(etp.flags);
    encodeContextTag(seq, 0, flags);

    auto key = encodeEncryptionKey(etp.session_key);
    encodeContextTag(seq, 1, key);

    auto crealm = encodeGeneralString(etp.cname.realm);
    encodeContextTag(seq, 2, crealm);

    auto cname = encodePrincipalName(etp.cname);
    encodeContextTag(seq, 3, cname);

    /* TransitedEncoding: tr-type=1 (domain-x500-compress), contents=transited string */
    {
        std::vector<uint8_t> trSeq;
        auto trType = encodeInteger(1);
        encodeContextTag(trSeq, 0, trType);
        std::vector<uint8_t> trContents(etp.transited.begin(), etp.transited.end());
        auto trOctet = encodeOctetString(trContents);
        encodeContextTag(trSeq, 1, trOctet);
        auto transitedEnc = encodeSequence(trSeq);
        encodeContextTag(seq, 4, transitedEnc);
    }

    auto authtime = encodeGeneralizedTime(etp.authtime);
    encodeContextTag(seq, 5, authtime);

    if (etp.starttime > 0) {
        auto starttime = encodeGeneralizedTime(etp.starttime);
        encodeContextTag(seq, 6, starttime);
    }

    auto endtime = encodeGeneralizedTime(etp.endtime);
    encodeContextTag(seq, 7, endtime);

    if (etp.renew_till > 0) {
        auto renewTill = encodeGeneralizedTime(etp.renew_till);
        encodeContextTag(seq, 8, renewTill);
    }

    auto encSeq = encodeSequence(seq);

    /* Wrap in APPLICATION 3 */
    std::vector<uint8_t> result;
    encodeTLV(result, APP_ENC_TICKET_PART, encSeq);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeEncKdcRepPart(const Krb5EncKdcRepPart &erp,
                                                    bool isAsRep) {
    /* EncKDCRepPart ::= SEQUENCE {
     *   key          [0] EncryptionKey,
     *   last-req     [1] LastReq,
     *   nonce        [2] UInt32,
     *   flags        [4] TicketFlags,
     *   authtime     [5] KerberosTime,
     *   starttime    [6] KerberosTime OPTIONAL,
     *   endtime      [7] KerberosTime,
     *   renew-till   [8] KerberosTime OPTIONAL,
     *   srealm       [9] Realm,
     *   sname        [10] PrincipalName,
     * } */
    std::vector<uint8_t> seq;

    auto key = encodeEncryptionKey(erp.session_key);
    encodeContextTag(seq, 0, key);

    /* last-req: empty SEQUENCE (required but can be empty) */
    {
        auto emptySeq = encodeSequence({});
        encodeContextTag(seq, 1, emptySeq);
    }

    auto nonce = encodeUInteger(erp.nonce);
    encodeContextTag(seq, 2, nonce);

    /* [3] key-expiration OPTIONAL - omitted */

    auto flags = encodeBitString(erp.flags);
    encodeContextTag(seq, 4, flags);

    auto authtime = encodeGeneralizedTime(erp.authtime);
    encodeContextTag(seq, 5, authtime);

    if (erp.starttime > 0) {
        auto starttime = encodeGeneralizedTime(erp.starttime);
        encodeContextTag(seq, 6, starttime);
    }

    auto endtime = encodeGeneralizedTime(erp.endtime);
    encodeContextTag(seq, 7, endtime);

    if (erp.renew_till > 0) {
        auto renewTill = encodeGeneralizedTime(erp.renew_till);
        encodeContextTag(seq, 8, renewTill);
    }

    auto srealm = encodeGeneralString(erp.sname.realm);
    encodeContextTag(seq, 9, srealm);

    auto sname = encodePrincipalName(erp.sname);
    encodeContextTag(seq, 10, sname);

    auto encSeq = encodeSequence(seq);

    /* Wrap in APPLICATION tag:
     * EncASRepPart = [APPLICATION 25], EncTGSRepPart = [APPLICATION 26] */
    uint8_t appTag = isAsRep ? APP_ENC_AS_REP_PART : APP_ENC_TGS_REP_PART;
    std::vector<uint8_t> result;
    encodeTLV(result, appTag, encSeq);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeAuthenticator(const Krb5Authenticator &auth) {
    /* Authenticator ::= [APPLICATION 2] SEQUENCE {
     *   authenticator-vno [0] INTEGER,
     *   crealm            [1] Realm,
     *   cname             [2] PrincipalName,
     *   cusec             [4] Microseconds,
     *   ctime             [5] KerberosTime,
     *   seq-number        [7] UInt32 OPTIONAL,
     * } */
    std::vector<uint8_t> seq;

    auto vno = encodeInteger(auth.authenticator_vno);
    encodeContextTag(seq, 0, vno);

    auto crealm = encodeGeneralString(auth.cname.realm);
    encodeContextTag(seq, 1, crealm);

    auto cname = encodePrincipalName(auth.cname);
    encodeContextTag(seq, 2, cname);

    /* [3] cksum OPTIONAL - omitted */

    auto cusec = encodeUInteger(auth.cusec);
    encodeContextTag(seq, 4, cusec);

    auto ctime = encodeGeneralizedTime(auth.ctime);
    encodeContextTag(seq, 5, ctime);

    /* [6] subkey OPTIONAL - omitted */

    if (auth.seq_number > 0) {
        auto seqnum = encodeUInteger(auth.seq_number);
        encodeContextTag(seq, 7, seqnum);
    }

    auto authSeq = encodeSequence(seq);

    /* Wrap in APPLICATION 2 */
    std::vector<uint8_t> result;
    encodeTLV(result, APP_AUTHENTICATOR, authSeq);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeApReq(const Krb5ApReq &apReq) {
    /* AP-REQ ::= [APPLICATION 14] SEQUENCE {
     *   pvno          [0] INTEGER,
     *   msg-type      [1] INTEGER,
     *   ap-options    [2] APOptions,
     *   ticket        [3] Ticket,
     *   authenticator [4] EncryptedData
     * } */
    std::vector<uint8_t> seq;

    auto pvno = encodeInteger(apReq.pvno);
    encodeContextTag(seq, 0, pvno);

    auto msgType = encodeInteger(static_cast<int32_t>(apReq.msg_type));
    encodeContextTag(seq, 1, msgType);

    auto apOpts = encodeBitString(apReq.ap_options);
    encodeContextTag(seq, 2, apOpts);

    auto ticket = encodeTicket(apReq.ticket);
    encodeContextTag(seq, 3, ticket);

    auto authEnc = encodeEncryptedData(apReq.auth_etype, apReq.auth_kvno,
                                        apReq.authenticator);
    encodeContextTag(seq, 4, authEnc);

    auto apReqSeq = encodeSequence(seq);

    std::vector<uint8_t> result;
    encodeTLV(result, APP_AP_REQ, apReqSeq);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeApRep(const Krb5ApRep &apRep) {
    /* AP-REP ::= [APPLICATION 15] SEQUENCE {
     *   pvno     [0] INTEGER,
     *   msg-type [1] INTEGER,
     *   enc-part [2] EncryptedData
     * } */
    std::vector<uint8_t> seq;

    auto pvno = encodeInteger(apRep.pvno);
    encodeContextTag(seq, 0, pvno);

    auto msgType = encodeInteger(static_cast<int32_t>(apRep.msg_type));
    encodeContextTag(seq, 1, msgType);

    auto encPart = encodeEncryptedData(apRep.enc_etype, apRep.enc_kvno, apRep.enc_part);
    encodeContextTag(seq, 2, encPart);

    auto apRepSeq = encodeSequence(seq);

    std::vector<uint8_t> result;
    encodeTLV(result, APP_AP_REP, apRepSeq);
    return result;
}

std::vector<uint8_t> Asn1Der::encodeKrbError(const Krb5Error &err) {
    /* KRB-ERROR ::= [APPLICATION 30] SEQUENCE {
     *   pvno       [0] INTEGER,
     *   msg-type   [1] INTEGER,
     *   stime      [4] KerberosTime,
     *   susec      [5] Microseconds,
     *   error-code [6] Int32,
     *   realm      [9] Realm,
     *   sname      [10] PrincipalName,
     *   e-text     [11] KerberosString OPTIONAL,
     *   e-data     [12] OCTET STRING OPTIONAL
     * } */
    std::vector<uint8_t> seq;

    auto pvno = encodeInteger(err.pvno);
    encodeContextTag(seq, 0, pvno);

    auto msgType = encodeInteger(static_cast<int32_t>(err.msg_type));
    encodeContextTag(seq, 1, msgType);

    /* [2] ctime OPTIONAL - omitted */
    /* [3] cusec OPTIONAL - omitted */

    auto stime = encodeGeneralizedTime(err.stime);
    encodeContextTag(seq, 4, stime);

    auto susec = encodeUInteger(err.susec);
    encodeContextTag(seq, 5, susec);

    auto errorCode = encodeInteger(static_cast<int32_t>(err.error_code));
    encodeContextTag(seq, 6, errorCode);

    /* [7] crealm OPTIONAL - omitted */
    /* [8] cname OPTIONAL - omitted */

    auto realm = encodeGeneralString(err.sname.realm);
    encodeContextTag(seq, 9, realm);

    auto sname = encodePrincipalName(err.sname);
    encodeContextTag(seq, 10, sname);

    if (!err.e_text.empty()) {
        auto etext = encodeGeneralString(err.e_text);
        encodeContextTag(seq, 11, etext);
    }

    if (!err.e_data.empty()) {
        auto edata = encodeOctetString(err.e_data);
        encodeContextTag(seq, 12, edata);
    }

    auto errSeq = encodeSequence(seq);

    std::vector<uint8_t> result;
    encodeTLV(result, APP_KRB_ERROR, errSeq);
    return result;
}

std::vector<uint8_t> Asn1Der::encodePaData(int32_t paType,
                                             const std::vector<uint8_t> &paValue) {
    /* PA-DATA ::= SEQUENCE {
     *   padata-type  [1] Int32,
     *   padata-value [2] OCTET STRING
     * } */
    std::vector<uint8_t> seq;

    auto pt = encodeInteger(paType);
    encodeContextTag(seq, 1, pt);

    auto pv = encodeOctetString(paValue);
    encodeContextTag(seq, 2, pv);

    return encodeSequence(seq);
}

std::vector<uint8_t> Asn1Der::encodePaEncTimestamp(time_t timestamp, uint32_t usec) {
    /* PA-ENC-TS-ENC ::= SEQUENCE {
     *   patimestamp [0] KerberosTime,
     *   pausec      [1] Microseconds OPTIONAL
     * } */
    std::vector<uint8_t> seq;

    auto ts = encodeGeneralizedTime(timestamp);
    encodeContextTag(seq, 0, ts);

    if (usec > 0) {
        auto us = encodeUInteger(usec);
        encodeContextTag(seq, 1, us);
    }

    return encodeSequence(seq);
}

std::vector<uint8_t> Asn1Der::encodeEtypeInfo2(Krb5EncType etype,
                                                 const std::string &salt) {
    /* ETYPE-INFO2-ENTRY ::= SEQUENCE {
     *   etype  [0] Int32,
     *   salt   [1] KerberosString OPTIONAL,
     * } */
    std::vector<uint8_t> entry;

    auto et = encodeInteger(static_cast<int32_t>(etype));
    encodeContextTag(entry, 0, et);

    if (!salt.empty()) {
        auto s = encodeGeneralString(salt);
        encodeContextTag(entry, 1, s);
    }

    auto entrySeq = encodeSequence(entry);

    /* Wrap in SEQUENCE OF (single entry) */
    return encodeSequence(entrySeq);
}

/* ---- Decoding ---- */

bool Asn1Der::parseTLV(const uint8_t *data, size_t len, size_t &off,
                        uint8_t &tag, const uint8_t *&content, size_t &content_len) {
    if (off >= len) return false;

    tag = data[off++];

    /* Parse length */
    if (off >= len) return false;
    uint8_t lb = data[off++];

    if (lb < 0x80) {
        content_len = lb;
    } else {
        int nbytes = lb & 0x7F;
        if (nbytes == 0 || nbytes > 4 || off + nbytes > len) return false;
        content_len = 0;
        for (int i = 0; i < nbytes; ++i) {
            content_len = (content_len << 8) | data[off++];
        }
    }

    if (off + content_len > len) return false;
    content = data + off;
    off += content_len;
    return true;
}

bool Asn1Der::parseInteger(const uint8_t *data, size_t len, int32_t &val) {
    if (len == 0) return false;
    val = (data[0] & 0x80) ? -1 : 0;
    for (size_t i = 0; i < len; ++i) {
        val = (val << 8) | data[i];
    }
    return true;
}

bool Asn1Der::parseUInteger(const uint8_t *data, size_t len, uint32_t &val) {
    if (len == 0) return false;
    val = 0;
    for (size_t i = 0; i < len; ++i) {
        val = (val << 8) | data[i];
    }
    return true;
}

bool Asn1Der::parseOctetString(const uint8_t *data, size_t len,
                                std::vector<uint8_t> &out) {
    out.assign(data, data + len);
    return true;
}

bool Asn1Der::parseGeneralString(const uint8_t *data, size_t len, std::string &out) {
    out.assign(reinterpret_cast<const char*>(data), len);
    return true;
}

bool Asn1Der::parseGeneralizedTime(const uint8_t *data, size_t len, time_t &t) {
    if (len < 15) return false; /* YYYYMMDDHHmmSSZ */
    std::string ts(reinterpret_cast<const char*>(data), len);

    struct tm utc = {};
    if (sscanf(ts.c_str(), "%4d%2d%2d%2d%2d%2d",
               &utc.tm_year, &utc.tm_mon, &utc.tm_mday,
               &utc.tm_hour, &utc.tm_min, &utc.tm_sec) != 6)
        return false;
    utc.tm_year -= 1900;
    utc.tm_mon -= 1;
    t = portable_timegm(&utc);
    return true;
}

bool Asn1Der::parseBitString(const uint8_t *data, size_t len, uint32_t &flags) {
    if (len < 1) return false;
    /* uint8_t unused = data[0]; */
    flags = 0;
    for (size_t i = 1; i < len && i <= 4; ++i) {
        flags = (flags << 8) | data[i];
    }
    /* Shift to fill 32 bits if fewer bytes */
    size_t nbytes = len - 1;
    if (nbytes < 4) {
        flags <<= (4 - nbytes) * 8;
    }
    return true;
}

bool Asn1Der::parsePrincipalName(const uint8_t *data, size_t len,
                                  Krb5PrincipalType &type,
                                  std::vector<std::string> &components) {
    /* PrincipalName ::= SEQUENCE {
     *   name-type   [0] Int32,
     *   name-string [1] SEQUENCE OF GeneralString
     * } */
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    /* Expect SEQUENCE */
    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;

    /* [0] name-type */
    if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) return false;
    if ((tag & 0x1F) != 0) return false;
    {
        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;
        if (!parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) return false;
        int32_t nt;
        if (!parseInteger(icontent, icontent_len, nt)) return false;
        type = static_cast<Krb5PrincipalType>(nt);
    }

    /* [1] name-string */
    if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) return false;
    if ((tag & 0x1F) != 1) return false;
    {
        size_t noff = 0;
        uint8_t ntag;
        const uint8_t *ncontent;
        size_t ncontent_len;
        /* SEQUENCE OF GeneralString */
        if (!parseTLV(content, content_len, noff, ntag, ncontent, ncontent_len)) return false;
        if (ntag != TAG_SEQUENCE) return false;

        size_t nseqoff = 0;
        components.clear();
        while (nseqoff < ncontent_len) {
            uint8_t stag;
            const uint8_t *scontent;
            size_t scontent_len;
            if (!parseTLV(ncontent, ncontent_len, nseqoff, stag, scontent, scontent_len))
                break;
            if (stag == TAG_GENERAL_STRING) {
                std::string s;
                parseGeneralString(scontent, scontent_len, s);
                components.push_back(s);
            }
        }
    }

    return !components.empty();
}

bool Asn1Der::parseEncryptedData(const uint8_t *data, size_t len,
                                  Krb5EncType &etype, uint32_t &kvno,
                                  std::vector<uint8_t> &cipher) {
    /* EncryptedData ::= SEQUENCE {
     *   etype  [0] Int32,
     *   kvno   [1] UInt32 OPTIONAL,
     *   cipher [2] OCTET STRING
     * } */
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;
    kvno = 0;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 0: /* etype */
                if (!parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    return false;
                {
                    int32_t et;
                    if (!parseInteger(icontent, icontent_len, et)) return false;
                    etype = static_cast<Krb5EncType>(et);
                }
                break;
            case 1: /* kvno */
                if (!parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    return false;
                parseUInteger(icontent, icontent_len, kvno);
                break;
            case 2: /* cipher */
                if (!parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    return false;
                parseOctetString(icontent, icontent_len, cipher);
                break;
        }
    }

    return !cipher.empty();
}

bool Asn1Der::parseEncryptionKey(const uint8_t *data, size_t len,
                                  Krb5SessionKey &key) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 0: /* keytype */
                if (!parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    return false;
                {
                    int32_t kt;
                    if (!parseInteger(icontent, icontent_len, kt)) return false;
                    key.enctype = static_cast<Krb5EncType>(kt);
                }
                break;
            case 1: /* keyvalue */
                if (!parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    return false;
                parseOctetString(icontent, icontent_len, key.keydata);
                break;
        }
    }

    return !key.keydata.empty();
}

bool Asn1Der::parseTicket(const uint8_t *data, size_t len, Krb5Ticket &ticket) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    /* Expect APPLICATION 1 wrapper */
    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != APP_TICKET) return false;

    const uint8_t *appData = content;
    size_t appLen = content_len;
    size_t aoff = 0;

    /* Expect SEQUENCE */
    if (!parseTLV(appData, appLen, aoff, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;
    std::string realm;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 0: /* tkt-vno */
                if (!parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    return false;
                {
                    int32_t vno;
                    if (!parseInteger(icontent, icontent_len, vno)) return false;
                    ticket.tkt_vno = static_cast<uint8_t>(vno);
                }
                break;
            case 1: /* realm */
                if (!parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    return false;
                parseGeneralString(icontent, icontent_len, realm);
                break;
            case 2: /* sname */
            {
                Krb5PrincipalType ptype;
                std::vector<std::string> components;
                if (parsePrincipalName(content, content_len, ptype, components)) {
                    ticket.sname.type = ptype;
                    ticket.sname.realm = realm;
                    if (components.size() >= 1) ticket.sname.name = components[0];
                    if (components.size() >= 2) ticket.sname.instance = components[1];
                }
                break;
            }
            case 3: /* enc-part */
                parseEncryptedData(content, content_len,
                                   ticket.enc_etype, ticket.enc_kvno, ticket.enc_part);
                break;
        }
    }

    return !ticket.enc_part.empty();
}

bool Asn1Der::parseKdcReq(const uint8_t *data, size_t len, Krb5KdcReq &req) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    /* Expect APPLICATION 10 or 12 wrapper */
    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != APP_AS_REQ && tag != APP_TGS_REQ) return false;

    req.msg_type = (tag == APP_AS_REQ) ? KRB_AS_REQ : KRB_TGS_REQ;

    /* Expect SEQUENCE */
    size_t aoff = 0;
    if (!parseTLV(content, content_len, aoff, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;
    std::string reqRealm;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 1: /* pvno */
                if (!parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    break;
                {
                    int32_t pvno;
                    parseInteger(icontent, icontent_len, pvno);
                    req.pvno = static_cast<uint8_t>(pvno);
                }
                break;
            case 2: /* msg-type */
                if (!parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    break;
                {
                    int32_t mt;
                    parseInteger(icontent, icontent_len, mt);
                    req.msg_type = static_cast<Krb5MsgType>(mt);
                }
                break;
            case 3: /* padata */
                /* Store the raw padata SEQUENCE for processing */
                req.padata.assign(content, content + content_len);
                /* For TGS-REQ, extract PA-TGS-REQ (AP-REQ) */
                if (req.msg_type == KRB_TGS_REQ) {
                    /* Parse SEQUENCE OF PA-DATA, find PA-TGS-REQ (type 1) */
                    size_t poff = 0;
                    uint8_t ptag;
                    const uint8_t *pcontent;
                    size_t pcontent_len;
                    if (parseTLV(content, content_len, poff, ptag, pcontent, pcontent_len)) {
                        if (ptag == TAG_SEQUENCE) {
                            /* This is the SEQUENCE OF PA-DATA */
                            /* Actually content IS directly the SEQUENCE OF PA-DATA */
                        }
                    }
                }
                break;
            case 4: /* req-body */
            {
                /* KDC-REQ-BODY SEQUENCE */
                size_t boff = 0;
                uint8_t btag;
                const uint8_t *bcontent;
                size_t bcontent_len;
                if (!parseTLV(content, content_len, boff, btag, bcontent, bcontent_len))
                    break;
                if (btag != TAG_SEQUENCE) break;

                size_t bboff = 0;
                while (bboff < bcontent_len) {
                    uint8_t bbtag;
                    const uint8_t *bbcontent;
                    size_t bbcontent_len;
                    if (!parseTLV(bcontent, bcontent_len, bboff, bbtag, bbcontent, bbcontent_len))
                        break;
                    uint8_t bbtagnum = bbtag & 0x1F;

                    size_t fioff = 0;
                    uint8_t fitag;
                    const uint8_t *ficontent;
                    size_t ficontent_len;

                    switch (bbtagnum) {
                        case 0: /* kdc-options */
                            if (parseTLV(bbcontent, bbcontent_len, fioff, fitag, ficontent, ficontent_len))
                                parseBitString(ficontent, ficontent_len, req.kdc_options);
                            break;
                        case 1: /* cname */
                        {
                            Krb5PrincipalType ptype;
                            std::vector<std::string> components;
                            if (parsePrincipalName(bbcontent, bbcontent_len, ptype, components)) {
                                req.cname.type = ptype;
                                if (components.size() >= 1) req.cname.name = components[0];
                                if (components.size() >= 2) req.cname.instance = components[1];
                            }
                            break;
                        }
                        case 2: /* realm */
                            if (parseTLV(bbcontent, bbcontent_len, fioff, fitag, ficontent, ficontent_len))
                                parseGeneralString(ficontent, ficontent_len, reqRealm);
                            req.cname.realm = reqRealm;
                            req.sname.realm = reqRealm;
                            break;
                        case 3: /* sname */
                        {
                            Krb5PrincipalType ptype;
                            std::vector<std::string> components;
                            if (parsePrincipalName(bbcontent, bbcontent_len, ptype, components)) {
                                req.sname.type = ptype;
                                req.sname.realm = reqRealm;
                                if (components.size() >= 1) req.sname.name = components[0];
                                if (components.size() >= 2) req.sname.instance = components[1];
                            }
                            break;
                        }
                        case 4: /* from */
                            if (parseTLV(bbcontent, bbcontent_len, fioff, fitag, ficontent, ficontent_len))
                                parseGeneralizedTime(ficontent, ficontent_len, req.from);
                            break;
                        case 5: /* till */
                            if (parseTLV(bbcontent, bbcontent_len, fioff, fitag, ficontent, ficontent_len))
                                parseGeneralizedTime(ficontent, ficontent_len, req.till);
                            break;
                        case 6: /* rtime */
                            if (parseTLV(bbcontent, bbcontent_len, fioff, fitag, ficontent, ficontent_len))
                                parseGeneralizedTime(ficontent, ficontent_len, req.rtime);
                            break;
                        case 7: /* nonce */
                            if (parseTLV(bbcontent, bbcontent_len, fioff, fitag, ficontent, ficontent_len))
                                parseUInteger(ficontent, ficontent_len, req.nonce);
                            break;
                        case 8: /* etype */
                        {
                            size_t etoff = 0;
                            uint8_t ettag;
                            const uint8_t *etcontent;
                            size_t etcontent_len;
                            if (parseTLV(bbcontent, bbcontent_len, etoff, ettag, etcontent, etcontent_len)) {
                                if (ettag == TAG_SEQUENCE) {
                                    size_t eoff = 0;
                                    while (eoff < etcontent_len) {
                                        uint8_t etag2;
                                        const uint8_t *econtent;
                                        size_t econtent_len;
                                        if (!parseTLV(etcontent, etcontent_len, eoff, etag2, econtent, econtent_len))
                                            break;
                                        int32_t et;
                                        if (parseInteger(econtent, econtent_len, et))
                                            req.etypes.push_back(static_cast<Krb5EncType>(et));
                                    }
                                }
                            }
                            break;
                        }
                        case 11: /* additional-tickets */
                        {
                            /* For TGS-REQ, this may contain the TGT
                             * Actually in standard Kerberos, additional-tickets is
                             * used for user-to-user. The TGT is in the PA-TGS-REQ. */
                            break;
                        }
                    }
                }
                break;
            }
        }
    }

    return true;
}

bool Asn1Der::parseKdcRep(const uint8_t *data, size_t len, Krb5KdcRep &rep) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    /* APPLICATION wrapper */
    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != APP_AS_REP && tag != APP_TGS_REP) return false;

    rep.msg_type = (tag == APP_AS_REP) ? KRB_AS_REP : KRB_TGS_REP;

    size_t aoff = 0;
    if (!parseTLV(content, content_len, aoff, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;
    std::string crealm;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 0: /* pvno */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    int32_t pvno;
                    parseInteger(icontent, icontent_len, pvno);
                    rep.pvno = static_cast<uint8_t>(pvno);
                }
                break;
            case 1: /* msg-type */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    int32_t mt;
                    parseInteger(icontent, icontent_len, mt);
                    rep.msg_type = static_cast<Krb5MsgType>(mt);
                }
                break;
            case 3: /* crealm */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralString(icontent, icontent_len, crealm);
                rep.cname.realm = crealm;
                break;
            case 4: /* cname */
            {
                Krb5PrincipalType ptype;
                std::vector<std::string> components;
                if (parsePrincipalName(content, content_len, ptype, components)) {
                    rep.cname.type = ptype;
                    rep.cname.realm = crealm;
                    if (components.size() >= 1) rep.cname.name = components[0];
                    if (components.size() >= 2) rep.cname.instance = components[1];
                }
                break;
            }
            case 5: /* ticket */
                parseTicket(content, content_len, rep.ticket);
                break;
            case 6: /* enc-part */
                parseEncryptedData(content, content_len,
                                   rep.enc_etype, rep.enc_kvno, rep.enc_part);
                break;
        }
    }

    return true;
}

bool Asn1Der::parseEncTicketPart(const uint8_t *data, size_t len,
                                  Krb5EncTicketPart &etp) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    /* APPLICATION 3 wrapper */
    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != APP_ENC_TICKET_PART) return false;

    size_t aoff = 0;
    if (!parseTLV(content, content_len, aoff, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;
    std::string crealm;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 0: /* flags */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseBitString(icontent, icontent_len, etp.flags);
                break;
            case 1: /* key */
                parseEncryptionKey(content, content_len, etp.session_key);
                break;
            case 2: /* crealm */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    parseGeneralString(icontent, icontent_len, crealm);
                    etp.cname.realm = crealm;
                }
                break;
            case 3: /* cname */
            {
                Krb5PrincipalType ptype;
                std::vector<std::string> components;
                if (parsePrincipalName(content, content_len, ptype, components)) {
                    etp.cname.type = ptype;
                    etp.cname.realm = crealm;
                    if (components.size() >= 1) etp.cname.name = components[0];
                    if (components.size() >= 2) etp.cname.instance = components[1];
                }
                break;
            }
            case 4: /* transited */
            {
                /* TransitedEncoding ::= SEQUENCE { tr-type[0], contents[1] } */
                size_t toff = 0;
                uint8_t ttag;
                const uint8_t *tcontent;
                size_t tcontent_len;
                if (parseTLV(content, content_len, toff, ttag, tcontent, tcontent_len)) {
                    if (ttag == TAG_SEQUENCE) {
                        size_t tsoff = 0;
                        while (tsoff < tcontent_len) {
                            uint8_t tstag;
                            const uint8_t *tscontent;
                            size_t tscontent_len;
                            if (!parseTLV(tcontent, tcontent_len, tsoff, tstag, tscontent, tscontent_len))
                                break;
                            if ((tstag & 0x1F) == 1) {
                                size_t tooff = 0;
                                uint8_t totag;
                                const uint8_t *tocontent;
                                size_t tocontent_len;
                                if (parseTLV(tscontent, tscontent_len, tooff, totag, tocontent, tocontent_len))
                                    etp.transited.assign(reinterpret_cast<const char*>(tocontent), tocontent_len);
                            }
                        }
                    }
                }
                break;
            }
            case 5: /* authtime */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, etp.authtime);
                break;
            case 6: /* starttime */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, etp.starttime);
                break;
            case 7: /* endtime */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, etp.endtime);
                break;
            case 8: /* renew-till */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, etp.renew_till);
                break;
        }
    }

    return true;
}

bool Asn1Der::parseEncKdcRepPart(const uint8_t *data, size_t len,
                                  Krb5EncKdcRepPart &erp) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    /* APPLICATION 25 or 26 wrapper */
    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != APP_ENC_AS_REP_PART && tag != APP_ENC_TGS_REP_PART) return false;

    size_t aoff = 0;
    if (!parseTLV(content, content_len, aoff, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;
    std::string srealm;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 0: /* key */
                parseEncryptionKey(content, content_len, erp.session_key);
                break;
            case 1: /* last-req - skip */
                break;
            case 2: /* nonce */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseUInteger(icontent, icontent_len, erp.nonce);
                break;
            case 4: /* flags */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseBitString(icontent, icontent_len, erp.flags);
                break;
            case 5: /* authtime */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, erp.authtime);
                break;
            case 6: /* starttime */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, erp.starttime);
                break;
            case 7: /* endtime */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, erp.endtime);
                break;
            case 8: /* renew-till */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, erp.renew_till);
                break;
            case 9: /* srealm */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    parseGeneralString(icontent, icontent_len, srealm);
                    erp.sname.realm = srealm;
                }
                break;
            case 10: /* sname */
            {
                Krb5PrincipalType ptype;
                std::vector<std::string> components;
                if (parsePrincipalName(content, content_len, ptype, components)) {
                    erp.sname.type = ptype;
                    erp.sname.realm = srealm;
                    if (components.size() >= 1) erp.sname.name = components[0];
                    if (components.size() >= 2) erp.sname.instance = components[1];
                }
                break;
            }
        }
    }

    return true;
}

bool Asn1Der::parseAuthenticator(const uint8_t *data, size_t len,
                                  Krb5Authenticator &auth) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    /* APPLICATION 2 wrapper */
    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != APP_AUTHENTICATOR) return false;

    size_t aoff = 0;
    if (!parseTLV(content, content_len, aoff, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;
    std::string crealm;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 0: /* authenticator-vno */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    int32_t vno;
                    parseInteger(icontent, icontent_len, vno);
                    auth.authenticator_vno = static_cast<uint8_t>(vno);
                }
                break;
            case 1: /* crealm */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    parseGeneralString(icontent, icontent_len, crealm);
                    auth.cname.realm = crealm;
                }
                break;
            case 2: /* cname */
            {
                Krb5PrincipalType ptype;
                std::vector<std::string> components;
                if (parsePrincipalName(content, content_len, ptype, components)) {
                    auth.cname.type = ptype;
                    auth.cname.realm = crealm;
                    if (components.size() >= 1) auth.cname.name = components[0];
                    if (components.size() >= 2) auth.cname.instance = components[1];
                }
                break;
            }
            case 4: /* cusec */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseUInteger(icontent, icontent_len, auth.cusec);
                break;
            case 5: /* ctime */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, auth.ctime);
                break;
            case 7: /* seq-number */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseUInteger(icontent, icontent_len, auth.seq_number);
                break;
        }
    }

    return true;
}

bool Asn1Der::parseApReq(const uint8_t *data, size_t len, Krb5ApReq &apReq) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    /* APPLICATION 14 wrapper */
    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != APP_AP_REQ) return false;

    size_t aoff = 0;
    if (!parseTLV(content, content_len, aoff, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 0: /* pvno */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    int32_t pvno;
                    parseInteger(icontent, icontent_len, pvno);
                    apReq.pvno = static_cast<uint8_t>(pvno);
                }
                break;
            case 1: /* msg-type */
                break; /* already known */
            case 2: /* ap-options */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseBitString(icontent, icontent_len, apReq.ap_options);
                break;
            case 3: /* ticket */
                parseTicket(content, content_len, apReq.ticket);
                break;
            case 4: /* authenticator (EncryptedData) */
                parseEncryptedData(content, content_len,
                                   apReq.auth_etype, apReq.auth_kvno, apReq.authenticator);
                break;
        }
    }

    return true;
}

bool Asn1Der::parseApRep(const uint8_t *data, size_t len, Krb5ApRep &apRep) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != APP_AP_REP) return false;

    size_t aoff = 0;
    if (!parseTLV(content, content_len, aoff, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 0: /* pvno */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    int32_t pvno;
                    parseInteger(icontent, icontent_len, pvno);
                    apRep.pvno = static_cast<uint8_t>(pvno);
                }
                break;
            case 2: /* enc-part */
                parseEncryptedData(content, content_len,
                                   apRep.enc_etype, apRep.enc_kvno, apRep.enc_part);
                break;
        }
    }

    return true;
}

bool Asn1Der::parseKrbError(const uint8_t *data, size_t len, Krb5Error &err) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != APP_KRB_ERROR) return false;

    size_t aoff = 0;
    if (!parseTLV(content, content_len, aoff, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    const uint8_t *seqData = content;
    size_t seqLen = content_len;
    size_t soff = 0;
    std::string realm;

    while (soff < seqLen) {
        if (!parseTLV(seqData, seqLen, soff, tag, content, content_len)) break;
        uint8_t tagnum = tag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (tagnum) {
            case 0: /* pvno */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    int32_t pvno;
                    parseInteger(icontent, icontent_len, pvno);
                    err.pvno = static_cast<uint8_t>(pvno);
                }
                break;
            case 4: /* stime */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, err.stime);
                break;
            case 5: /* susec */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseUInteger(icontent, icontent_len, err.susec);
                break;
            case 6: /* error-code */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    int32_t ec;
                    parseInteger(icontent, icontent_len, ec);
                    err.error_code = static_cast<Krb5ErrorCode>(ec);
                }
                break;
            case 9: /* realm */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len)) {
                    parseGeneralString(icontent, icontent_len, realm);
                    err.sname.realm = realm;
                }
                break;
            case 10: /* sname */
            {
                Krb5PrincipalType ptype;
                std::vector<std::string> components;
                if (parsePrincipalName(content, content_len, ptype, components)) {
                    err.sname.type = ptype;
                    err.sname.realm = realm;
                    if (components.size() >= 1) err.sname.name = components[0];
                    if (components.size() >= 2) err.sname.instance = components[1];
                }
                break;
            }
            case 11: /* e-text */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralString(icontent, icontent_len, err.e_text);
                break;
            case 12: /* e-data */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseOctetString(icontent, icontent_len, err.e_data);
                break;
        }
    }

    return true;
}

bool Asn1Der::parsePaEncTimestamp(const uint8_t *data, size_t len,
                                   time_t &ts, uint32_t &usec) {
    size_t off = 0;
    uint8_t tag;
    const uint8_t *content;
    size_t content_len;

    if (!parseTLV(data, len, off, tag, content, content_len)) return false;
    if (tag != TAG_SEQUENCE) return false;

    size_t soff = 0;
    ts = 0;
    usec = 0;

    while (soff < content_len) {
        uint8_t stag;
        const uint8_t *scontent;
        size_t scontent_len;
        if (!parseTLV(content, content_len, soff, stag, scontent, scontent_len)) break;
        uint8_t stagnum = stag & 0x1F;

        size_t ioff = 0;
        uint8_t itag;
        const uint8_t *icontent;
        size_t icontent_len;

        switch (stagnum) {
            case 0: /* patimestamp */
                if (parseTLV(scontent, scontent_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, ts);
                break;
            case 1: /* pausec */
                if (parseTLV(scontent, scontent_len, ioff, itag, icontent, icontent_len))
                    parseUInteger(icontent, icontent_len, usec);
                break;
        }
    }

    return ts > 0;
}

bool Asn1Der::findContextTag(const uint8_t *data, size_t len,
                              uint8_t tagnum,
                              const uint8_t *&content, size_t &content_len) {
    size_t off = 0;
    uint8_t expectedTag = CLASS_CONTEXT | CONSTRUCTED | (tagnum & 0x1F);

    while (off < len) {
        uint8_t tag;
        const uint8_t *tcontent;
        size_t tcontent_len;
        if (!parseTLV(data, len, off, tag, tcontent, tcontent_len)) return false;
        if (tag == expectedTag) {
            content = tcontent;
            content_len = tcontent_len;
            return true;
        }
    }
    return false;
}


/******************************************************************************
 * Krb5Principal
 *****************************************************************************/
Krb5Principal::Krb5Principal() : type(KRB5_NT_UNKNOWN) {}

Krb5Principal::Krb5Principal(Krb5PrincipalType t, const std::string &r,
                             const std::string &n, const std::string &inst)
    : type(t), realm(r), name(n), instance(inst) {}

std::vector<uint8_t> Krb5Principal::serialize() const {
    return Asn1Der::encodePrincipalName(*this);
}

bool Krb5Principal::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    Krb5PrincipalType ptype;
    std::vector<std::string> components;
    if (!Asn1Der::parsePrincipalName(data, len, ptype, components))
        return false;
    type = ptype;
    if (components.size() >= 1) name = components[0];
    if (components.size() >= 2) instance = components[1];
    consumed = len; /* consumed entire input */
    return true;
}

std::vector<std::string> Krb5Principal::nameComponents() const {
    std::vector<std::string> result;
    result.push_back(name);
    if (!instance.empty())
        result.push_back(instance);
    return result;
}

std::string Krb5Principal::toString() const {
    std::string result = name;
    if (!instance.empty())
        result += "/" + instance;
    result += "@" + realm;
    return result;
}

/******************************************************************************
 * Krb5SessionKey
 *****************************************************************************/
Krb5SessionKey::Krb5SessionKey() : enctype(KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1) {}

std::vector<uint8_t> Krb5SessionKey::serialize() const {
    return Asn1Der::encodeEncryptionKey(*this);
}

bool Krb5SessionKey::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    if (!Asn1Der::parseEncryptionKey(data, len, *this))
        return false;
    consumed = len;
    return true;
}

/******************************************************************************
 * Krb5Ticket
 *****************************************************************************/
Krb5Ticket::Krb5Ticket()
    : tkt_vno(5), enc_etype(KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1), enc_kvno(1) {}

std::vector<uint8_t> Krb5Ticket::serialize() const {
    return Asn1Der::encodeTicket(*this);
}

bool Krb5Ticket::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    if (!Asn1Der::parseTicket(data, len, *this))
        return false;
    consumed = len;
    return true;
}

/******************************************************************************
 * Krb5EncTicketPart
 *****************************************************************************/
Krb5EncTicketPart::Krb5EncTicketPart()
    : flags(KRB5_TF_INITIAL | KRB5_TF_PRE_AUTHENT),
      authtime(0), starttime(0), endtime(0), renew_till(0) {}

std::vector<uint8_t> Krb5EncTicketPart::serialize() const {
    return Asn1Der::encodeEncTicketPart(*this);
}

bool Krb5EncTicketPart::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    if (!Asn1Der::parseEncTicketPart(data, len, *this))
        return false;
    consumed = len;
    return true;
}

/******************************************************************************
 * Krb5Authenticator
 *****************************************************************************/
Krb5Authenticator::Krb5Authenticator()
    : authenticator_vno(5), ctime(0), cusec(0), seq_number(0) {}

std::vector<uint8_t> Krb5Authenticator::serialize() const {
    return Asn1Der::encodeAuthenticator(*this);
}

bool Krb5Authenticator::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    if (!Asn1Der::parseAuthenticator(data, len, *this))
        return false;
    consumed = len;
    return true;
}

/******************************************************************************
 * Krb5KdcReq
 *****************************************************************************/
Krb5KdcReq::Krb5KdcReq()
    : pvno(5), msg_type(KRB_AS_REQ), kdc_options(0),
      from(0), till(0), rtime(0), nonce(0) {
    etypes.push_back(KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
    etypes.push_back(KRB5_ENCTYPE_AES128_CTS_HMAC_SHA1);
}

std::vector<uint8_t> Krb5KdcReq::serialize() const {
    return Asn1Der::encodeKdcReq(*this);
}

bool Krb5KdcReq::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    if (!Asn1Der::parseKdcReq(data, len, *this))
        return false;
    consumed = len;
    return true;
}

/******************************************************************************
 * Krb5KdcRep
 *****************************************************************************/
Krb5KdcRep::Krb5KdcRep()
    : pvno(5), msg_type(KRB_AS_REP),
      enc_etype(KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1), enc_kvno(1) {}

std::vector<uint8_t> Krb5KdcRep::serialize() const {
    return Asn1Der::encodeKdcRep(*this);
}

bool Krb5KdcRep::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    if (!Asn1Der::parseKdcRep(data, len, *this))
        return false;
    consumed = len;
    return true;
}

/******************************************************************************
 * Krb5EncKdcRepPart
 *****************************************************************************/
Krb5EncKdcRepPart::Krb5EncKdcRepPart()
    : nonce(0), flags(0), authtime(0), starttime(0), endtime(0), renew_till(0) {}

std::vector<uint8_t> Krb5EncKdcRepPart::serialize(bool isAsRep) const {
    return Asn1Der::encodeEncKdcRepPart(*this, isAsRep);
}

bool Krb5EncKdcRepPart::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    if (!Asn1Der::parseEncKdcRepPart(data, len, *this))
        return false;
    consumed = len;
    return true;
}

/******************************************************************************
 * Krb5ApReq
 *****************************************************************************/
Krb5ApReq::Krb5ApReq()
    : pvno(5), msg_type(KRB_AP_REQ), ap_options(0),
      auth_etype(KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1), auth_kvno(0) {}

std::vector<uint8_t> Krb5ApReq::serialize() const {
    return Asn1Der::encodeApReq(*this);
}

bool Krb5ApReq::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    if (!Asn1Der::parseApReq(data, len, *this))
        return false;
    consumed = len;
    return true;
}

/******************************************************************************
 * Krb5ApRep
 *****************************************************************************/
Krb5ApRep::Krb5ApRep()
    : pvno(5), msg_type(KRB_AP_REP),
      enc_etype(KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1), enc_kvno(0) {}

std::vector<uint8_t> Krb5ApRep::serialize() const {
    return Asn1Der::encodeApRep(*this);
}

bool Krb5ApRep::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    if (!Asn1Der::parseApRep(data, len, *this))
        return false;
    consumed = len;
    return true;
}

/******************************************************************************
 * Krb5Error
 *****************************************************************************/
Krb5Error::Krb5Error()
    : pvno(5), msg_type(KRB_ERROR), stime(0), susec(0),
      error_code(KDC_ERR_NONE) {}

std::vector<uint8_t> Krb5Error::serialize() const {
    return Asn1Der::encodeKrbError(*this);
}

bool Krb5Error::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
    if (!Asn1Der::parseKrbError(data, len, *this))
        return false;
    consumed = len;
    return true;
}

/******************************************************************************
 * Krb5Crypto - Encryption operations
 *
 * RFC 3961/3962 compliant key derivation and encryption for MIT/Heimdal
 * compatibility.  Uses proper PBKDF2-HMAC-SHA1, AES-CTS (Cipher Text
 * Stealing), and HMAC-SHA1-96 checksums.
 *
 * Supported enctypes:
 *   17 = aes128-cts-hmac-sha1-96  (RFC 3962)
 *   18 = aes256-cts-hmac-sha1-96  (RFC 3962)
 *   19 = aes128-cts-hmac-sha256-128 (RFC 8009)
 *   20 = aes256-cts-hmac-sha384-192 (RFC 8009)
 *****************************************************************************/

size_t Krb5Crypto::keyLength(Krb5EncType etype) {
    switch (etype) {
        case KRB5_ENCTYPE_AES128_CTS_HMAC_SHA1:
        case KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256:
            return 16;
        case KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1:
        case KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384:
            return 32;
        default:
            return 16;
    }
}

Krb5EncType Krb5Crypto::selectEncType(const std::vector<Krb5EncType> &etypes) {
    /* Preference order: aes256-sha1 > aes128-sha1 > aes256-sha384 > aes128-sha256 */
    static const Krb5EncType pref[] = {
        KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1,
        KRB5_ENCTYPE_AES128_CTS_HMAC_SHA1,
        KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384,
        KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256
    };
    for (auto p : pref) {
        for (auto e : etypes) {
            if (e == p) return e;
        }
    }
    return etypes.empty() ? KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1 : etypes[0];
}

std::vector<uint8_t> Krb5Crypto::nfold(const std::vector<uint8_t> &input, size_t nbytes) {
    /*
     * n-fold operation – direct port of MIT krb5int_nfold (nfold.c).
     * input length = inbits (in bytes); output = nbytes bytes (outbits bits).
     */
    int inbytes  = static_cast<int>(input.size());
    int outbytes = static_cast<int>(nbytes);

    /* lcm(inbytes, outbytes) */
    int a = outbytes, b = inbytes, c;
    while (b != 0) { c = b; b = a % b; a = c; }
    int lcmVal = outbytes * inbytes / a;

    std::vector<uint8_t> out(outbytes, 0);
    int byte = 0;

    for (int i = lcmVal - 1; i >= 0; --i) {
        /* compute the msbit in k which gets added into this byte */
        int msbit = (
            ((inbytes << 3) - 1)
            + (((inbytes << 3) + 13) * (i / inbytes))
            + ((inbytes - (i % inbytes)) << 3)
        ) % (inbytes << 3);

        /* pull out the byte value itself */
        int idx1 = ((inbytes - 1) - (msbit >> 3)) % inbytes;
        int idx2 = ((inbytes) - (msbit >> 3)) % inbytes;
        if (idx1 < 0) idx1 += inbytes;
        if (idx2 < 0) idx2 += inbytes;
        byte += (((input[idx1] << 8) | input[idx2])
                  >> ((msbit & 7) + 1)) & 0xff;

        /* do the addition */
        byte += out[i % outbytes];
        out[i % outbytes] = byte & 0xff;

        /* keep around the carry bit, if any */
        byte >>= 8;
    }

    /* if there's a carry bit left over, add it back in */
    if (byte) {
        for (int i = outbytes - 1; i >= 0; --i) {
            byte += out[i];
            out[i] = byte & 0xff;
            byte >>= 8;
        }
    }

    return out;
}

std::vector<uint8_t> Krb5Crypto::dk(const std::vector<uint8_t> &key,
                                      const std::vector<uint8_t> &constant,
                                      size_t keyLen) {
    /*
     * DK(Key, Constant) = random-to-key(DR(Key, Constant))
     * DR uses n-fold of the constant to AES block size, then
     * encrypts repeatedly in ECB mode to generate enough key material.
     *
     * For AES, random-to-key is identity, so DK = DR.
     * RFC 3962 §4: DR uses AES-ECB (single block encrypt).
     */
    size_t blockSize = 16; /* AES block size */
    auto folded = nfold(constant, blockSize);

    std::vector<uint8_t> result;
    std::vector<uint8_t> input = folded;

    while (result.size() < keyLen) {
        /* AES-ECB encrypt: single block with key */
        std::vector<uint8_t> encrypted;
        if (key.size() == 16) {
            netplus::aes128 cipher(key);
            encrypted = cipher.encrypt(input);
        } else {
            netplus::aes256 cipher(key);
            encrypted = cipher.encrypt(input);
        }
        result.insert(result.end(), encrypted.begin(),
                      encrypted.begin() + std::min(blockSize, encrypted.size()));
        input = encrypted;
        input.resize(blockSize);
    }

    result.resize(keyLen);
    return result;
}

std::vector<uint8_t> Krb5Crypto::pbkdf2HmacSha1(const std::string &password,
                                                    const std::vector<uint8_t> &salt,
                                                    uint32_t iterations,
                                                    size_t keyLen) {
    /*
     * PBKDF2-HMAC-SHA1 per RFC 2898 / RFC 3962
     * Uses the proper netplus::pbkdf2_hmac_sha1 with real HMAC-SHA1.
     */
    std::vector<uint8_t> pw(password.begin(), password.end());
    return netplus::pbkdf2_hmac_sha1(pw, salt, iterations, keyLen);
}

std::vector<uint8_t> Krb5Crypto::string2key(const std::string &password,
                                              const std::string &salt,
                                              Krb5EncType etype) {
    /*
     * RFC 3962 string-to-key for AES encryption types:
     *   tkey = PBKDF2-HMAC-SHA1(password, salt, iterations, keyLen)
     *   key = DK(tkey, "kerberos")
     *
     * For AES-128: keyLen = 16, iterations = 4096
     * For AES-256: keyLen = 32, iterations = 4096
     */
    size_t kLen = keyLength(etype);
    uint32_t iterations = 4096;

    std::vector<uint8_t> saltBytes(salt.begin(), salt.end());
    auto tkey = pbkdf2HmacSha1(password, saltBytes, iterations, kLen);

    /* DK(tkey, "kerberos") */
    std::string kerbStr = "kerberos";
    std::vector<uint8_t> kerbConst(kerbStr.begin(), kerbStr.end());
    auto finalKey = dk(tkey, kerbConst, kLen);

    return finalKey;
}

std::vector<uint8_t> Krb5Crypto::deriveKey(const std::vector<uint8_t> &baseKey,
                                             Krb5KeyUsage usage,
                                             Krb5EncType etype) {
    /*
     * RFC 3961 key derivation:
     *   Ke = DK(baseKey, usage || 0xAA)  -- encryption sub-key
     *   Ki = DK(baseKey, usage || 0x55)  -- integrity sub-key
     *
     * Usage is encoded as a 4-byte big-endian integer.
     * The 5th byte selects sub-key type (0xAA=encrypt, 0x55=integrity).
     */
    size_t kLen = keyLength(etype);
    std::vector<uint8_t> constant;
    constant.push_back(static_cast<uint8_t>((usage >> 24) & 0xFF));
    constant.push_back(static_cast<uint8_t>((usage >> 16) & 0xFF));
    constant.push_back(static_cast<uint8_t>((usage >> 8) & 0xFF));
    constant.push_back(static_cast<uint8_t>(usage & 0xFF));
    constant.push_back(0xAA); /* encryption sub-key */
    return dk(baseKey, constant, kLen);
}

Krb5SessionKey Krb5Crypto::generateSessionKey(Krb5EncType etype) {
    Krb5SessionKey sk;
    sk.enctype = etype;
    sk.keydata = randomBytes(keyLength(etype));
    return sk;
}

std::vector<uint8_t> Krb5Crypto::encrypt(const std::vector<uint8_t> &key,
                                           const std::vector<uint8_t> &plaintext,
                                           Krb5KeyUsage usage) {
    /*
     * RFC 3962 §5 simplified profile encryption:
     *
     * For aes128/256-cts-hmac-sha1-96:
     *   Ke = DK(key, usage || 0xAA)   -- encryption key
     *   Ki = DK(key, usage || 0x55)   -- integrity key
     *   confounder = 16 random bytes
     *   payload = confounder || plaintext
     *   checksum = HMAC-SHA1(Ki, payload)[0..11]   (12 bytes = 96 bits)
     *   ciphertext = AES-CTS-encrypt(Ke, payload) || checksum
     *
     * The IV for CTS is all-zero per RFC 3962 §5.
     */
    size_t kLen = keyLength(KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
    if (key.size() <= 16) kLen = 16;

    /* Derive Ke (encryption) and Ki (integrity) sub-keys */
    std::vector<uint8_t> usageEnc;
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 24) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 16) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 8) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>(static_cast<uint32_t>(usage) & 0xFF));

    std::vector<uint8_t> usageInt = usageEnc;
    usageEnc.push_back(0xAA);
    usageInt.push_back(0x55);

    auto Ke = dk(key, usageEnc, kLen);
    auto Ki = dk(key, usageInt, kLen);

    /* Generate 16-byte random confounder */
    auto confounder = randomBytes(16);

    /* Build payload: confounder || plaintext */
    std::vector<uint8_t> payload;
    payload.reserve(16 + plaintext.size());
    payload.insert(payload.end(), confounder.begin(), confounder.end());
    payload.insert(payload.end(), plaintext.begin(), plaintext.end());

    /* Compute HMAC-SHA1 checksum over payload, truncate to 12 bytes */
    auto hmac_full = netplus::hmac_sha1(Ki, payload);
    std::vector<uint8_t> cksum(hmac_full.begin(), hmac_full.begin() + 12);

    /* Pad payload to at least one AES block for CTS */
    if (payload.size() < 16) {
        payload.resize(16, 0);
    }

    /* AES-CTS encrypt with zero IV */
    std::vector<uint8_t> zeroIv(16, 0);
    std::vector<uint8_t> encrypted;
    if (Ke.size() == 16) {
        netplus::aes128 cipher(Ke);
        encrypted = cipher.encryptCTS(payload, zeroIv);
    } else {
        netplus::aes256 cipher(Ke);
        encrypted = cipher.encryptCTS(payload, zeroIv);
    }

    /* Output: ciphertext || HMAC (12 bytes) */
    encrypted.insert(encrypted.end(), cksum.begin(), cksum.end());
    return encrypted;
}

std::vector<uint8_t> Krb5Crypto::decrypt(const std::vector<uint8_t> &key,
                                           const std::vector<uint8_t> &ciphertext,
                                           Krb5KeyUsage usage) {
    /*
     * RFC 3962 §5 simplified profile decryption:
     *   Split input into encrypted_data (all but last 12 bytes) and checksum (last 12)
     *   Decrypt encrypted_data with AES-CTS using Ke
     *   Verify HMAC-SHA1-96 over decrypted data using Ki
     *   Strip 16-byte confounder from beginning
     */
    constexpr size_t CKSUM_LEN = 12;
    constexpr size_t CONFOUNDER_LEN = 16;

    /* Minimum: 16 (confounder) + 12 (checksum) = 28 bytes,
     * but encrypted part must be >= 16 (one AES block) */
    if (ciphertext.size() < CONFOUNDER_LEN + CKSUM_LEN) {
        throw AuthBackendError("Krb5Crypto::decrypt: ciphertext too short");
    }

    size_t kLen = keyLength(KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
    if (key.size() <= 16) kLen = 16;

    /* Derive Ke and Ki */
    std::vector<uint8_t> usageEnc;
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 24) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 16) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 8) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>(static_cast<uint32_t>(usage) & 0xFF));

    std::vector<uint8_t> usageInt = usageEnc;
    usageEnc.push_back(0xAA);
    usageInt.push_back(0x55);

    auto Ke = dk(key, usageEnc, kLen);
    auto Ki = dk(key, usageInt, kLen);

    /* Split: encrypted_data || HMAC-SHA1-96 (12 bytes) */
    size_t enc_len = ciphertext.size() - CKSUM_LEN;
    std::vector<uint8_t> enc_data(ciphertext.begin(), ciphertext.begin() + enc_len);
    std::vector<uint8_t> received_cksum(ciphertext.begin() + enc_len, ciphertext.end());

    /* AES-CTS decrypt with zero IV */
    std::vector<uint8_t> zeroIv(16, 0);
    std::vector<uint8_t> decrypted;
    if (Ke.size() == 16) {
        netplus::aes128 cipher(Ke);
        decrypted = cipher.decryptCTS(enc_data, zeroIv);
    } else {
        netplus::aes256 cipher(Ke);
        decrypted = cipher.decryptCTS(enc_data, zeroIv);
    }

    /* Verify HMAC-SHA1-96 over decrypted payload (confounder || plaintext) */
    auto hmac_full = netplus::hmac_sha1(Ki, decrypted);
    std::vector<uint8_t> computed_cksum(hmac_full.begin(), hmac_full.begin() + CKSUM_LEN);

    uint8_t diff = 0;
    for (size_t i = 0; i < CKSUM_LEN; ++i)
        diff |= computed_cksum[i] ^ received_cksum[i];
    if (diff != 0)
        throw AuthBackendError("Krb5Crypto::decrypt: HMAC-SHA1-96 verification failed");

    /* Strip confounder (first 16 bytes) */
    if (decrypted.size() < CONFOUNDER_LEN)
        throw AuthBackendError("Krb5Crypto::decrypt: decrypted data too short");

    return std::vector<uint8_t>(decrypted.begin() + CONFOUNDER_LEN, decrypted.end());
}

std::vector<uint8_t> Krb5Crypto::checksum(const std::vector<uint8_t> &key,
                                            const std::vector<uint8_t> &data,
                                            Krb5KeyUsage usage) {
    /*
     * RFC 3961 §5.4: checksum = HMAC-SHA1(Ki, data)[0..11]
     * Ki = DK(key, usage || 0x55)
     *
     * For aes128/256-cts-hmac-sha1-96, the checksum is 12 bytes.
     */
    size_t kLen = key.size();

    std::vector<uint8_t> usageInt;
    usageInt.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 24) & 0xFF));
    usageInt.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 16) & 0xFF));
    usageInt.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 8) & 0xFF));
    usageInt.push_back(static_cast<uint8_t>(static_cast<uint32_t>(usage) & 0xFF));
    usageInt.push_back(0x99); /* checksum key derivation constant */

    auto Kc = dk(key, usageInt, kLen);
    auto hmac_full = netplus::hmac_sha1(Kc, data);

    /* Truncate to 12 bytes (96 bits) for hmac-sha1-96 */
    return std::vector<uint8_t>(hmac_full.begin(), hmac_full.begin() + 12);
}

bool Krb5Crypto::verifyChecksum(const std::vector<uint8_t> &key,
                                  const std::vector<uint8_t> &data,
                                  const std::vector<uint8_t> &mac,
                                  Krb5KeyUsage usage) {
    auto computed = checksum(key, data, usage);
    if (computed.size() != mac.size()) return false;
    uint8_t diff = 0;
    for (size_t i = 0; i < computed.size(); ++i)
        diff |= computed[i] ^ mac[i];
    return diff == 0;
}

std::vector<uint8_t> Krb5Crypto::randomBytes(size_t count) {
    std::vector<uint8_t> buf(count);
    std::ifstream urandom("/dev/urandom", std::ios::binary);
    if (urandom.is_open()) {
        urandom.read(reinterpret_cast<char*>(buf.data()), count);
        if (static_cast<size_t>(urandom.gcount()) == count)
            return buf;
    }
    std::random_device rd;
    for (size_t i = 0; i < count; ++i)
        buf[i] = static_cast<uint8_t>(rd() & 0xFF);
    return buf;
}

uint32_t Krb5Crypto::randomNonce() {
    auto bytes = randomBytes(4);
    return (static_cast<uint32_t>(bytes[0]) << 24) |
           (static_cast<uint32_t>(bytes[1]) << 16) |
           (static_cast<uint32_t>(bytes[2]) << 8)  |
           static_cast<uint32_t>(bytes[3]);
}

/*******************************************************************************
 * RealmInfo
 ******************************************************************************/
RealmInfo::RealmInfo() : backend(nullptr), owned(false) {}

RealmInfo::RealmInfo(AuthBackend *b, bool own, const std::string &domain)
    : backend(b), owned(own), domainName(domain)
{
    tgsKey = Krb5Crypto::randomBytes(32);
}

/*******************************************************************************
 * CrossRealmKey
 ******************************************************************************/
CrossRealmKey::CrossRealmKey() {}

CrossRealmKey::CrossRealmKey(const std::string &local, const std::string &remote,
                             const std::vector<uint8_t> &k)
    : localRealm(local), remoteRealm(remote), key(k) {}

/******************************************************************************
 * Krb5KDC - Key Distribution Center
 *
 * MIT Kerberos / Heimdal compatible KDC with multi-realm support.
 * Each domain in the admin backend becomes its own Kerberos realm.
 * Realm name = upper-cased domain name.
 * Cross-realm referral TGTs enable service access across realms.
 *****************************************************************************/
namespace {
    class KdcEvent : public netplus::event {
    public:
        KdcEvent(std::vector<netplus::socket*> sockets, Krb5KDC *kdc)
            : netplus::event(sockets), _kdc(kdc) {}

        void RequestEvent(netplus::con &curcon, const int tid, ULONG_PTR args) override {
            _kdc->RequestEvent(curcon, tid, args);
        }
        void ResponseEvent(netplus::con &curcon, const int tid, ULONG_PTR args) override {
            _kdc->ResponseEvent(curcon, tid, args);
        }
        void ConnectEvent(netplus::con &curcon, const int tid, ULONG_PTR args) override {
            _kdc->ConnectEvent(curcon, tid, args);
        }
        void DisconnectEvent(netplus::con &curcon, const int tid, ULONG_PTR args) override {
            _kdc->DisconnectEvent(curcon, tid, args);
        }
        void CreateConnection(std::shared_ptr<netplus::con> &res) override {
            _kdc->CreateConnection(res);
        }
    private:
        Krb5KDC *_kdc;
    };
}

Krb5KDC::Krb5KDC(AuthBackend &adminBackend,
                  const std::string &bindAddr,
                  int port, int maxconn)
    : _AdminBackend(adminBackend),
      _MaxClockSkew(300), _TicketLifetime(36000), _RenewableLifetime(604800),
      _Event(nullptr)
{
    /* Enumerate all domains and create one realm per domain */
    loadRealms();

    /* Generate cross-realm trust keys between all known realms */
    generateCrossRealmKeys();

    /* Create a TCP server socket for the KDC on the given port */
    netplus::tcp *ssock = new netplus::tcp(bindAddr.c_str(), port, maxconn, 0);
    _ServerSockets.push_back(ssock);
}

void Krb5KDC::init() {
    for (auto *s : _ServerSockets) {
        s->bind();
        s->setNonBlock();
        s->listen();
    }
}

Krb5KDC::~Krb5KDC() {
    delete static_cast<KdcEvent*>(_Event);
    for (auto *s : _ServerSockets)
        delete s;
    for (auto &kv : _Realms) {
        if (kv.second.owned)
            delete kv.second.backend;
    }
}

void Krb5KDC::loadRealms() {
    /* Clear existing owned backends (except admin) */
    for (auto it = _Realms.begin(); it != _Realms.end(); ) {
        if (it->second.owned) {
            delete it->second.backend;
            it = _Realms.erase(it);
        } else {
            ++it;
        }
    }

    /* Register the admin backend as a realm */
    std::string adminRealm = toUpperRealm("admin.local");
    if (_Realms.find(adminRealm) == _Realms.end()) {
        _Realms.emplace(adminRealm, RealmInfo(&_AdminBackend, false, "admin.local"));
    }

    /* Iterate the admin backend to find all domains */
    Domain domain;
    std::vector<uuid::uuid> dids;
    domain.list(_AdminBackend, dids);

    for (auto &did : dids) {
        try {
            class DomainData ddat(did);
            size_t dpos = sizeof(AuthHeader);
            domain.info(_AdminBackend, ddat, dpos);

            std::string domainName = ddat.getDomainName();
            std::string realmName  = toUpperRealm(domainName);

            if (_Realms.find(realmName) == _Realms.end()) {
                AuthBackend *domBackend = new AuthBackend(
                    ddat.getStorageType(),
                    ddat.getStorageOptions(),
                    ddat.getDomainName()
                );
                _Realms.emplace(realmName, RealmInfo(domBackend, true, domainName));
                std::cerr << "KDC: loaded realm " << realmName
                          << " for domain " << domainName << std::endl;
            }
        } catch (const std::exception &e) {
            std::cerr << "KDC: failed to load domain: " << e.what() << std::endl;
        }
    }
}

void Krb5KDC::generateCrossRealmKeys() {
    /*
     * Generate shared cross-realm trust keys between all realm pairs.
     * This enables cross-realm TGT referrals:
     *   krbtgt/REALM_B@REALM_A is encrypted with the cross-realm key
     *   shared between REALM_A and REALM_B.
     *
     * In a production deployment, these would be configured explicitly
     * or derived from a shared secret. Here we auto-generate them.
     */
    _CrossRealmKeys.clear();
    std::vector<std::string> realms = getRealmNames();

    for (size_t i = 0; i < realms.size(); ++i) {
        for (size_t j = i + 1; j < realms.size(); ++j) {
            /* Generate a shared key for both directions */
            auto sharedKey = Krb5Crypto::randomBytes(32);

            /* krbtgt/REALM_J@REALM_I and krbtgt/REALM_I@REALM_J */
            _CrossRealmKeys.emplace_back(realms[i], realms[j], sharedKey);
            _CrossRealmKeys.emplace_back(realms[j], realms[i], sharedKey);

            std::cerr << "KDC: cross-realm trust established: "
                      << realms[i] << " <-> " << realms[j] << std::endl;
        }
    }
}

std::vector<std::string> Krb5KDC::getRealmNames() const {
    std::vector<std::string> names;
    names.reserve(_Realms.size());
    for (const auto &kv : _Realms)
        names.push_back(kv.first);
    return names;
}

RealmInfo *Krb5KDC::resolveRealm(const std::string &realm) {
    auto it = _Realms.find(realm);
    if (it == _Realms.end())
        return nullptr;
    return &it->second;
}

bool Krb5KDC::getCrossRealmKey(const std::string &localRealm,
                                const std::string &remoteRealm,
                                std::vector<uint8_t> &key) {
    for (const auto &crk : _CrossRealmKeys) {
        if (crk.localRealm == localRealm && crk.remoteRealm == remoteRealm) {
            key = crk.key;
            return true;
        }
    }
    return false;
}

void Krb5KDC::runKdc(ULONG_PTR args) {
    auto *ev = new KdcEvent(_ServerSockets, this);
    _Event = ev;
    ev->runEventloop(args);
}

void Krb5KDC::setMaxClockSkew(int seconds) {
    _MaxClockSkew = seconds;
}

void Krb5KDC::setTicketLifetime(int seconds) {
    _TicketLifetime = seconds;
}

void Krb5KDC::setRenewableLifetime(int seconds) {
    _RenewableLifetime = seconds;
}

void Krb5KDC::CreateConnection(std::shared_ptr<netplus::con> &res) {
    res = std::make_shared<netplus::con>(this);
}

void Krb5KDC::ConnectEvent(netplus::con &curcon, const int /*tid*/, ULONG_PTR /*args*/) {
    (void)curcon;
}

void Krb5KDC::DisconnectEvent(netplus::con &curcon, const int /*tid*/, ULONG_PTR /*args*/) {
    (void)curcon;
}

void Krb5KDC::ResponseEvent(netplus::con &curcon, const int /*tid*/, ULONG_PTR /*args*/) {
    (void)curcon;
}

void Krb5KDC::RequestEvent(netplus::con &curcon, const int /*tid*/, ULONG_PTR /*args*/) {
    /*
     * Standard Kerberos TCP framing: 4-byte big-endian length prefix
     * followed by the ASN.1 DER encoded message.
     */
    if (curcon.RecvData.size() < 4)
        return;

    const uint8_t *raw = reinterpret_cast<const uint8_t*>(curcon.RecvData.data());
    uint32_t msg_len = (static_cast<uint32_t>(raw[0]) << 24) |
                       (static_cast<uint32_t>(raw[1]) << 16) |
                       (static_cast<uint32_t>(raw[2]) << 8)  |
                       static_cast<uint32_t>(raw[3]);

    if (curcon.RecvData.size() < 4 + msg_len)
        return; /* incomplete */

    const uint8_t *asn1Data = raw + 4;
    size_t asn1Len = msg_len;

    std::vector<uint8_t> response;

    try {
        response = handleStandardRequest(asn1Data, asn1Len);
    } catch (const std::exception &e) {
        std::string errRealm = _Realms.empty() ? "UNKNOWN" : _Realms.begin()->first;
        response = buildStandardError(KRB_ERR_GENERIC, e.what(), errRealm, "krbtgt");
    }

    /* Frame the response with 4-byte length prefix */
    uint32_t respLen = static_cast<uint32_t>(response.size());
    char lenBuf[4];
    lenBuf[0] = static_cast<char>((respLen >> 24) & 0xFF);
    lenBuf[1] = static_cast<char>((respLen >> 16) & 0xFF);
    lenBuf[2] = static_cast<char>((respLen >> 8) & 0xFF);
    lenBuf[3] = static_cast<char>(respLen & 0xFF);
    curcon.SendData.append(lenBuf, 4);
    curcon.SendData.append(reinterpret_cast<const char*>(response.data()), response.size());

    /* Consume processed bytes */
    curcon.RecvData.erase(curcon.RecvData.begin(),
                          curcon.RecvData.begin() + 4 + msg_len);
}

/*
 * handleStandardRequest - Dispatch ASN.1 DER encoded Kerberos messages
 */
std::vector<uint8_t> Krb5KDC::handleStandardRequest(const uint8_t *data, size_t len) {
    if (len < 2) {
        std::string errRealm = _Realms.empty() ? "UNKNOWN" : _Realms.begin()->first;
        return buildStandardError(KRB_ERR_GENERIC, "Message too short", errRealm, "krbtgt");
    }

    /* Peek at the APPLICATION tag to determine message type */
    uint8_t appTag = data[0];

    switch (appTag) {
        case Asn1Der::APP_AS_REQ:
            return handleStandardAsReq(data, len);
        case Asn1Der::APP_TGS_REQ:
            return handleStandardTgsReq(data, len);
        default:
        {
            std::string errRealm = _Realms.empty() ? "UNKNOWN" : _Realms.begin()->first;
            return buildStandardError(KRB_ERR_GENERIC,
                                       "Unknown message type",
                                       errRealm, "krbtgt");
        }
    }
}

/*
 * handleStandardAsReq - Authentication Service (AS)
 *
 * Standard ASN.1 DER processing for AS-REQ.
 * MIT Kerberos and Heimdal compatible.
 */
std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t len) {
    std::lock_guard<std::mutex> guard(_Lock);

    Krb5KdcReq req;
    size_t consumed = 0;
    if (!req.deserialize(data, len, consumed)) {
        std::string errRealm = _Realms.empty() ? "UNKNOWN" : _Realms.begin()->first;
        return buildStandardError(KRB_ERR_GENERIC, "Malformed AS-REQ", errRealm, "krbtgt");
    }

    if (req.pvno != 5) {
        return buildStandardError(KDC_ERR_BAD_PVNO, "Unsupported protocol version",
                                   req.cname.realm, "krbtgt");
    }

    /* Resolve the realm from the request */
    RealmInfo *ri = resolveRealm(req.cname.realm);
    if (!ri || !ri->backend) {
        return buildStandardError(KDC_ERR_C_PRINCIPAL_UNKNOWN,
                                   "Unknown realm: " + req.cname.realm,
                                   req.cname.realm, "krbtgt");
    }

    AuthBackend &backend = *ri->backend;

    /* Select the best encryption type */
    Krb5EncType selectedEtype = Krb5Crypto::selectEncType(req.etypes);

    /* Look up the client's key */
    uuid::uuid uid;
    std::vector<uint8_t> client_key;
    bool found = false;

    if (req.cname.type == KRB5_NT_SRV_HST || req.cname.type == KRB5_NT_SRV_INST) {
        found = lookupService(backend, req.cname, uid, client_key);
        if (!found) found = lookupClient(backend, req.cname, uid, client_key);
    } else if (req.cname.type == KRB5_NT_PRINCIPAL || req.cname.type == KRB5_NT_ENTERPRISE) {
        /* MIT kinit host/foo sends KRB5_NT_PRINCIPAL with 2 components */
        if (!req.cname.instance.empty()) {
            found = lookupService(backend, req.cname, uid, client_key);
            if (!found) found = lookupClient(backend, req.cname, uid, client_key);
        }
        if (!found) found = lookupUser(backend, req.cname, uid, client_key);
    } else {
        found = lookupUser(backend, req.cname, uid, client_key);
        if (!found) found = lookupService(backend, req.cname, uid, client_key);
        if (!found) found = lookupClient(backend, req.cname, uid, client_key);
    }

    if (!found) {
        return buildStandardError(KDC_ERR_C_PRINCIPAL_UNKNOWN,
                                   "Principal not found: " + req.cname.toString(),
                                   req.cname.realm, "krbtgt");
    }

    /* Check pre-authentication */
    if (req.padata.empty()) {
        /* Send PREAUTH_REQUIRED with ETYPE-INFO2 */
        std::string salt = req.cname.realm + req.cname.name;
        if (!req.cname.instance.empty())
            salt += "/" + req.cname.instance;
        auto etypeInfo = Asn1Der::encodeEtypeInfo2(selectedEtype, salt);
        auto paEtypeInfo = Asn1Der::encodePaData(KRB5_PADATA_ETYPE_INFO2, etypeInfo);

        Krb5Error preAuthErr;
        preAuthErr.pvno = 5;
        preAuthErr.msg_type = KRB_ERROR;
        preAuthErr.stime = time(nullptr);
        preAuthErr.susec = 0;
        preAuthErr.error_code = KDC_ERR_PREAUTH_REQUIRED;
        preAuthErr.sname = Krb5Principal(KRB5_NT_SRV_INST, req.cname.realm,
                                          "krbtgt", req.cname.realm);
        preAuthErr.e_text = "Pre-authentication required";

        /* e-data: METHOD-DATA (SEQUENCE OF PA-DATA) */
        auto paEncTs = Asn1Der::encodePaData(KRB5_PADATA_ENC_TIMESTAMP, {});
        std::vector<uint8_t> methodData;
        methodData.insert(methodData.end(), paEncTs.begin(), paEncTs.end());
        methodData.insert(methodData.end(), paEtypeInfo.begin(), paEtypeInfo.end());
        preAuthErr.e_data = Asn1Der::encodeSequence(methodData);

        return preAuthErr.serialize();
    }

    /* Parse and verify PA-ENC-TIMESTAMP from padata */
    {
        /* padata is stored as a SEQUENCE OF PA-DATA.
         * Parse it to find PA-ENC-TIMESTAMP (type 2). */
        bool paVerified = false;
        bool foundPaEncTimestamp = false;
        size_t paOff = 0;
        uint8_t paTag;
        const uint8_t *paContent;
        size_t paContentLen;

        /* Try parsing as SEQUENCE OF PA-DATA */
        if (Asn1Der::parseTLV(req.padata.data(), req.padata.size(), paOff,
                               paTag, paContent, paContentLen)) {
            size_t poff = 0;

            /* If it's a SEQUENCE, iterate PA-DATA entries */
            const uint8_t *iterData = (paTag == Asn1Der::TAG_SEQUENCE) ? paContent : req.padata.data();
            size_t iterLen = (paTag == Asn1Der::TAG_SEQUENCE) ? paContentLen : req.padata.size();

            while (poff < iterLen) {
                uint8_t pdTag;
                const uint8_t *pdContent;
                size_t pdContentLen;
                if (!Asn1Der::parseTLV(iterData, iterLen, poff, pdTag, pdContent, pdContentLen))
                    break;

                if (pdTag != Asn1Der::TAG_SEQUENCE) continue;

                /* Parse PA-DATA fields */
                int32_t pdType = -1;
                std::vector<uint8_t> pdValue;
                size_t pdOff = 0;
                while (pdOff < pdContentLen) {
                    uint8_t fTag;
                    const uint8_t *fContent;
                    size_t fContentLen;
                    if (!Asn1Der::parseTLV(pdContent, pdContentLen, pdOff, fTag, fContent, fContentLen))
                        break;
                    uint8_t fTagnum = fTag & 0x1F;
                    size_t fioff = 0;
                    uint8_t fitag;
                    const uint8_t *ficontent;
                    size_t ficontent_len;
                    if (fTagnum == 1) { /* padata-type */
                        if (Asn1Der::parseTLV(fContent, fContentLen, fioff, fitag, ficontent, ficontent_len))
                            Asn1Der::parseInteger(ficontent, ficontent_len, pdType);
                    } else if (fTagnum == 2) { /* padata-value */
                        if (Asn1Der::parseTLV(fContent, fContentLen, fioff, fitag, ficontent, ficontent_len))
                            Asn1Der::parseOctetString(ficontent, ficontent_len, pdValue);
                    }
                }

                if (pdType == KRB5_PADATA_ENC_TIMESTAMP && !pdValue.empty()) {
                    foundPaEncTimestamp = true;
                    /* pdValue is the DER encoding of EncryptedData SEQUENCE;
                     * extract the cipher OCTET STRING before decrypting. */
                    Krb5EncType paEtype;
                    uint32_t paKvno;
                    std::vector<uint8_t> paCipher;
                    if (!Asn1Der::parseEncryptedData(pdValue.data(), pdValue.size(),
                                                      paEtype, paKvno, paCipher)) {
                        continue;
                    }
                    try {
                        auto pa_plain = Krb5Crypto::decrypt(client_key, paCipher,
                                                             KRB5_KU_AS_REQ_PA_ENC_TS);
                        time_t pa_time = 0;
                        uint32_t pa_usec = 0;
                        if (Asn1Der::parsePaEncTimestamp(pa_plain.data(), pa_plain.size(),
                                                         pa_time, pa_usec)) {
                            time_t now = time(nullptr);
                            if (std::abs(static_cast<long>(now - pa_time)) <= _MaxClockSkew) {
                                paVerified = true;
                            }
                        }
                    } catch (const AuthBackendError &) {
                        /* Decryption failed = wrong key */
                    }
                }
            }
        }

        if (!paVerified) {
            if (foundPaEncTimestamp) {
                /*
                 * PA-ENC-TIMESTAMP was present but decryption/verification
                 * failed → wrong password.  Return PREAUTH_FAILED so MIT
                 * kinit reports an error instead of looping.
                 */
                return buildStandardError(KDC_ERR_PREAUTH_FAILED,
                                           "Pre-authentication failed",
                                           req.cname.realm, "krbtgt");
            }

            /*
             * No PA-ENC-TIMESTAMP found at all (first speculative AS-REQ
             * or client sent only PA-FX-FAST).  Return PREAUTH_REQUIRED
             * with ETYPE-INFO2 so the client learns salt and enctype.
             */
            std::string salt = req.cname.realm + req.cname.name;
            if (!req.cname.instance.empty())
                salt += "/" + req.cname.instance;
            auto etypeInfo = Asn1Der::encodeEtypeInfo2(selectedEtype, salt);
            auto paEtypeInfo = Asn1Der::encodePaData(KRB5_PADATA_ETYPE_INFO2, etypeInfo);

            Krb5Error preAuthErr;
            preAuthErr.pvno = 5;
            preAuthErr.msg_type = KRB_ERROR;
            preAuthErr.stime = time(nullptr);
            preAuthErr.susec = 0;
            preAuthErr.error_code = KDC_ERR_PREAUTH_REQUIRED;
            preAuthErr.sname = Krb5Principal(KRB5_NT_SRV_INST, req.cname.realm,
                                              "krbtgt", req.cname.realm);
            preAuthErr.e_text = "Additional pre-authentication required";

            auto paEncTs = Asn1Der::encodePaData(KRB5_PADATA_ENC_TIMESTAMP, {});
            std::vector<uint8_t> methodData;
            methodData.insert(methodData.end(), paEncTs.begin(), paEncTs.end());
            methodData.insert(methodData.end(), paEtypeInfo.begin(), paEtypeInfo.end());
            preAuthErr.e_data = Asn1Der::encodeSequence(methodData);

            return preAuthErr.serialize();
        }
    }

    /* Generate TGT session key */
    Krb5SessionKey tgt_session_key = Krb5Crypto::generateSessionKey(selectedEtype);

    time_t now = time(nullptr);
    time_t endtime = now + _TicketLifetime;
    if (req.till != 0 && req.till < endtime)
        endtime = req.till;

    uint32_t tktFlags = KRB5_TF_INITIAL | KRB5_TF_PRE_AUTHENT;

    /* Mirror requested flags where appropriate */
    if (req.kdc_options & KRB5_TF_FORWARDABLE)
        tktFlags |= KRB5_TF_FORWARDABLE;

    /* Only grant RENEWABLE when the client explicitly requests it.
     * RENEWABLE_OK (bit 27 = 0x10) means the client tolerates a
     * renewable ticket, but verify_as_reply() in MIT kinit requires
     * renew_till <= request->till in that case.  To stay safe we
     * simply omit the flag unless KDC_OPT_RENEWABLE is present. */
    time_t renew_till_time = 0;
    if (req.kdc_options & KRB5_TF_RENEWABLE) {
        tktFlags |= KRB5_TF_RENEWABLE;
        renew_till_time = now + _RenewableLifetime;
        if (req.rtime > 0 && req.rtime < renew_till_time)
            renew_till_time = req.rtime;
    }

    /* Build EncTicketPart */
    Krb5EncTicketPart enc_tkt;
    enc_tkt.flags = tktFlags;
    enc_tkt.session_key = tgt_session_key;
    enc_tkt.cname = req.cname;
    enc_tkt.transited = ""; /* no transited realms for initial ticket */
    enc_tkt.authtime = now;
    enc_tkt.starttime = now;
    enc_tkt.endtime = endtime;
    enc_tkt.renew_till = renew_till_time;
    enc_tkt.uid = uid;

    auto enc_tkt_data = enc_tkt.serialize();
    auto enc_tkt_cipher = Krb5Crypto::encrypt(ri->tgsKey, enc_tkt_data, KRB5_KU_TICKET);

    /* Build TGT */
    Krb5Ticket tgt;
    tgt.tkt_vno = 5;
    tgt.sname = Krb5Principal(KRB5_NT_SRV_INST, req.cname.realm, "krbtgt", req.cname.realm);
    tgt.enc_etype = selectedEtype;
    tgt.enc_kvno = 1;
    tgt.enc_part = enc_tkt_cipher;

    /* Build EncKdcRepPart */
    Krb5EncKdcRepPart enc_rep;
    enc_rep.session_key = tgt_session_key;
    enc_rep.nonce = req.nonce;
    enc_rep.flags = tktFlags;
    enc_rep.authtime = now;
    enc_rep.starttime = now;
    enc_rep.endtime = endtime;
    enc_rep.renew_till = enc_tkt.renew_till;
    enc_rep.sname = tgt.sname;

    auto enc_rep_data = enc_rep.serialize(true /* isAsRep */);

    auto enc_rep_cipher = Krb5Crypto::encrypt(client_key, enc_rep_data, KRB5_KU_AS_REP_ENC_PART);

    /* Build AS-REP */
    Krb5KdcRep as_rep;
    as_rep.pvno = 5;
    as_rep.msg_type = KRB_AS_REP;
    as_rep.cname = req.cname;
    as_rep.ticket = tgt;
    as_rep.enc_etype = selectedEtype;
    as_rep.enc_kvno = 1;
    as_rep.enc_part = enc_rep_cipher;

    auto as_rep_bytes = as_rep.serialize();
    return as_rep_bytes;
}

/*
 * handleStandardTgsReq - Ticket-Granting Service (TGS)
 *
 * Standard ASN.1 DER processing for TGS-REQ.
 * Supports cross-realm referrals for multi-realm operation.
 */
std::vector<uint8_t> Krb5KDC::handleStandardTgsReq(const uint8_t *data, size_t len) {
    std::lock_guard<std::mutex> guard(_Lock);

    Krb5KdcReq req;
    size_t consumed = 0;
    if (!req.deserialize(data, len, consumed)) {
        std::string errRealm = _Realms.empty() ? "UNKNOWN" : _Realms.begin()->first;
        return buildStandardError(KRB_ERR_GENERIC, "Malformed TGS-REQ", errRealm, "krbtgt");
    }

    if (req.pvno != 5) {
        return buildStandardError(KDC_ERR_BAD_PVNO, "Unsupported protocol version",
                                   req.sname.realm, "krbtgt");
    }

    /*
     * Extract the TGT from PA-TGS-REQ (PA-DATA type 1, which contains an AP-REQ).
     * The AP-REQ contains the TGT ticket + encrypted authenticator.
     */
    Krb5ApReq tgsApReq;
    bool foundApReq = false;

    if (!req.padata.empty()) {
        /* Parse SEQUENCE OF PA-DATA to find PA-TGS-REQ */
        size_t paOff = 0;
        uint8_t paTag;
        const uint8_t *paContent;
        size_t paContentLen;

        if (Asn1Der::parseTLV(req.padata.data(), req.padata.size(), paOff,
                               paTag, paContent, paContentLen)) {
            const uint8_t *iterData = (paTag == Asn1Der::TAG_SEQUENCE) ? paContent : req.padata.data();
            size_t iterLen = (paTag == Asn1Der::TAG_SEQUENCE) ? paContentLen : req.padata.size();

            size_t poff = 0;
            while (poff < iterLen) {
                uint8_t pdTag;
                const uint8_t *pdContent;
                size_t pdContentLen;
                if (!Asn1Der::parseTLV(iterData, iterLen, poff, pdTag, pdContent, pdContentLen))
                    break;

                if (pdTag != Asn1Der::TAG_SEQUENCE) continue;

                int32_t pdType = -1;
                std::vector<uint8_t> pdValue;
                size_t pdOff = 0;
                while (pdOff < pdContentLen) {
                    uint8_t fTag;
                    const uint8_t *fContent;
                    size_t fContentLen;
                    if (!Asn1Der::parseTLV(pdContent, pdContentLen, pdOff, fTag, fContent, fContentLen))
                        break;
                    uint8_t fTagnum = fTag & 0x1F;
                    size_t fioff = 0;
                    uint8_t fitag;
                    const uint8_t *ficontent;
                    size_t ficontent_len;
                    if (fTagnum == 1) {
                        if (Asn1Der::parseTLV(fContent, fContentLen, fioff, fitag, ficontent, ficontent_len))
                            Asn1Der::parseInteger(ficontent, ficontent_len, pdType);
                    } else if (fTagnum == 2) {
                        if (Asn1Der::parseTLV(fContent, fContentLen, fioff, fitag, ficontent, ficontent_len))
                            Asn1Der::parseOctetString(ficontent, ficontent_len, pdValue);
                    }
                }

                if (pdType == KRB5_PADATA_TGS_REQ && !pdValue.empty()) {
                    /* pdValue is an AP-REQ containing the TGT */
                    size_t apConsumed = 0;
                    if (tgsApReq.deserialize(pdValue.data(), pdValue.size(), apConsumed)) {
                        foundApReq = true;
                    }
                }
            }
        }
    }

    if (!foundApReq) {
        return buildStandardError(KRB_AP_ERR_BAD_INTEGRITY,
                                   "No PA-TGS-REQ found in TGS-REQ",
                                   req.sname.realm, req.sname.name, req.sname.instance);
    }

    /* Determine the TGT's realm from the ticket's service principal */
    std::string tgtRealm = tgsApReq.ticket.sname.realm;
    bool isCrossRealmTgt = false;
    std::vector<uint8_t> tgtDecryptKey;

    /* Check if this is a cross-realm TGT (krbtgt/REMOTE@LOCAL) */
    if (tgsApReq.ticket.sname.name == "krbtgt" &&
        !tgsApReq.ticket.sname.instance.empty() &&
        tgsApReq.ticket.sname.instance != tgsApReq.ticket.sname.realm) {
        /* Cross-realm TGT */
        isCrossRealmTgt = true;
        std::string localRealm = tgsApReq.ticket.sname.realm;
        std::string remoteRealm = tgsApReq.ticket.sname.instance;

        /* The TGT was issued by localRealm for remoteRealm */
        if (!getCrossRealmKey(localRealm, remoteRealm, tgtDecryptKey)) {
            return buildStandardError(KDC_ERR_S_PRINCIPAL_UNKNOWN,
                                       "No cross-realm trust: " + localRealm + " -> " + remoteRealm,
                                       req.sname.realm, req.sname.name, req.sname.instance);
        }
    } else {
        /* Local TGT - decrypt with realm's TGS key */
        RealmInfo *ri = resolveRealm(tgtRealm);
        if (!ri || !ri->backend) {
            return buildStandardError(KDC_ERR_S_PRINCIPAL_UNKNOWN,
                                       "Unknown TGT realm: " + tgtRealm,
                                       req.sname.realm, req.sname.name, req.sname.instance);
        }
        tgtDecryptKey = ri->tgsKey;
    }

    /* Decrypt the TGT */
    Krb5EncTicketPart tgt_enc;
    try {
        auto tgt_plain = Krb5Crypto::decrypt(tgtDecryptKey,
                                              tgsApReq.ticket.enc_part,
                                              KRB5_KU_TICKET);
        size_t tgt_consumed = 0;
        if (!tgt_enc.deserialize(tgt_plain.data(), tgt_plain.size(), tgt_consumed)) {
            return buildStandardError(KRB_AP_ERR_BAD_INTEGRITY,
                                       "Cannot decode TGT",
                                       req.sname.realm, req.sname.name, req.sname.instance);
        }
    } catch (const AuthBackendError &) {
        return buildStandardError(KRB_AP_ERR_BAD_INTEGRITY,
                                   "TGT decryption failed",
                                   req.sname.realm, req.sname.name, req.sname.instance);
    }

    /* Check TGT validity */
    time_t now = time(nullptr);
    if (now > tgt_enc.endtime) {
        return buildStandardError(KRB_AP_ERR_TKT_EXPIRED, "TGT has expired",
                                   req.sname.realm, req.sname.name, req.sname.instance);
    }
    if (now < tgt_enc.starttime - _MaxClockSkew) {
        return buildStandardError(KRB_AP_ERR_TKT_NYV, "TGT not yet valid",
                                   req.sname.realm, req.sname.name, req.sname.instance);
    }

    /* Decrypt and verify the authenticator (encrypted with TGT session key) */
    Krb5Authenticator auth;
    try {
        auto auth_plain = Krb5Crypto::decrypt(tgt_enc.session_key.keydata,
                                               tgsApReq.authenticator,
                                               KRB5_KU_TGS_REQ_AUTH);
        size_t auth_consumed = 0;
        if (!auth.deserialize(auth_plain.data(), auth_plain.size(), auth_consumed)) {
            return buildStandardError(KRB_AP_ERR_BAD_INTEGRITY,
                                       "Cannot decode authenticator",
                                       req.sname.realm, req.sname.name, req.sname.instance);
        }
    } catch (const AuthBackendError &) {
        return buildStandardError(KRB_AP_ERR_BAD_INTEGRITY,
                                   "Authenticator decryption failed",
                                   req.sname.realm, req.sname.name, req.sname.instance);
    }

    /* Verify authenticator timestamp */
    if (std::abs(static_cast<long>(now - auth.ctime)) > _MaxClockSkew) {
        return buildStandardError(KRB_AP_ERR_SKEW, "Clock skew too great",
                                   req.sname.realm, req.sname.name, req.sname.instance);
    }

    /* Verify authenticator principal matches TGT */
    if (auth.cname.name != tgt_enc.cname.name ||
        auth.cname.realm != tgt_enc.cname.realm) {
        return buildStandardError(KRB_AP_ERR_BAD_INTEGRITY,
                                   "Authenticator principal mismatch",
                                   req.sname.realm, req.sname.name, req.sname.instance);
    }

    /* Select encryption type */
    Krb5EncType selectedEtype = Krb5Crypto::selectEncType(req.etypes);

    /*
     * Cross-realm referral logic:
     * If the requested service is in a different realm than the TGT,
     * issue a cross-realm referral TGT instead of the service ticket.
     */
    std::string svcRealm = req.sname.realm;
    if (svcRealm != tgtRealm && !isCrossRealmTgt) {
        /* Standard cross-realm referral: issue krbtgt/TARGET@LOCAL */
        return buildCrossRealmReferral(req, tgt_enc, svcRealm, selectedEtype);
    }

    /* Resolve the service's realm */
    RealmInfo *svcRi = resolveRealm(svcRealm);
    if (!svcRi || !svcRi->backend) {
        /* Service realm not local - try cross-realm referral */
        if (svcRealm != tgtRealm) {
            return buildCrossRealmReferral(req, tgt_enc, svcRealm, selectedEtype);
        }
        return buildStandardError(KDC_ERR_S_PRINCIPAL_UNKNOWN,
                                   "Unknown service realm: " + svcRealm,
                                   svcRealm, req.sname.name, req.sname.instance);
    }

    AuthBackend &svcBackend = *svcRi->backend;

    /* Look up the requested service */
    uuid::uuid svc_uid;
    std::vector<uint8_t> svc_key;
    bool svc_found = lookupService(svcBackend, req.sname, svc_uid, svc_key);
    if (!svc_found)
        svc_found = lookupClient(svcBackend, req.sname, svc_uid, svc_key);
    if (!svc_found)
        svc_found = lookupUser(svcBackend, req.sname, svc_uid, svc_key);
    if (!svc_found) {
        return buildStandardError(KDC_ERR_S_PRINCIPAL_UNKNOWN,
                                   "Service not found: " + req.sname.toString(),
                                   svcRealm, req.sname.name, req.sname.instance);
    }

    /* Generate service session key */
    Krb5SessionKey svc_session_key = Krb5Crypto::generateSessionKey(selectedEtype);

    time_t endtime = now + _TicketLifetime;
    if (req.till != 0 && req.till < endtime)
        endtime = req.till;
    if (endtime > tgt_enc.endtime)
        endtime = tgt_enc.endtime;

    uint32_t svcFlags = KRB5_TF_PRE_AUTHENT;
    if (tgt_enc.flags & KRB5_TF_FORWARDABLE)
        svcFlags |= KRB5_TF_FORWARDABLE;
    if (tgt_enc.flags & KRB5_TF_RENEWABLE)
        svcFlags |= KRB5_TF_RENEWABLE;

    /* Build EncTicketPart for service ticket */
    Krb5EncTicketPart svc_enc_tkt;
    svc_enc_tkt.flags = svcFlags;
    svc_enc_tkt.session_key = svc_session_key;
    svc_enc_tkt.cname = tgt_enc.cname;
    svc_enc_tkt.transited = tgt_enc.transited;
    svc_enc_tkt.authtime = tgt_enc.authtime;
    svc_enc_tkt.starttime = now;
    svc_enc_tkt.endtime = endtime;
    svc_enc_tkt.renew_till = std::min(tgt_enc.renew_till, now + _RenewableLifetime);
    svc_enc_tkt.uid = tgt_enc.uid;

    auto svc_enc_data = svc_enc_tkt.serialize();
    auto svc_enc_cipher = Krb5Crypto::encrypt(svc_key, svc_enc_data, KRB5_KU_TICKET);

    /* Build service ticket */
    Krb5Ticket svc_ticket;
    svc_ticket.tkt_vno = 5;
    svc_ticket.sname = req.sname;
    svc_ticket.enc_etype = selectedEtype;
    svc_ticket.enc_kvno = 1;
    svc_ticket.enc_part = svc_enc_cipher;

    /* Build EncKdcRepPart (encrypted with TGT session key) */
    Krb5EncKdcRepPart enc_rep;
    enc_rep.session_key = svc_session_key;
    enc_rep.nonce = req.nonce;
    enc_rep.flags = svcFlags;
    enc_rep.authtime = tgt_enc.authtime;
    enc_rep.starttime = now;
    enc_rep.endtime = endtime;
    enc_rep.renew_till = svc_enc_tkt.renew_till;
    enc_rep.sname = req.sname;

    auto enc_rep_data = enc_rep.serialize(false /* isAsRep */);
    auto enc_rep_cipher = Krb5Crypto::encrypt(tgt_enc.session_key.keydata,
                                                enc_rep_data, KRB5_KU_TGS_REP_ENC_PART);

    /* Build TGS-REP */
    Krb5KdcRep tgs_rep;
    tgs_rep.pvno = 5;
    tgs_rep.msg_type = KRB_TGS_REP;
    tgs_rep.cname = tgt_enc.cname;
    tgs_rep.ticket = svc_ticket;
    tgs_rep.enc_etype = selectedEtype;
    tgs_rep.enc_kvno = 1;
    tgs_rep.enc_part = enc_rep_cipher;

    return tgs_rep.serialize();
}

/*
 * buildCrossRealmReferral - Issue a cross-realm TGT referral
 *
 * Creates krbtgt/TARGET_REALM@LOCAL_REALM encrypted with the cross-realm
 * shared key. MIT Kerberos and Heimdal clients follow these referrals
 * automatically to traverse realm boundaries.
 */
std::vector<uint8_t> Krb5KDC::buildCrossRealmReferral(
    const Krb5KdcReq &req,
    const Krb5EncTicketPart &tgtEnc,
    const std::string &targetRealm,
    Krb5EncType etype)
{
    std::string localRealm = tgtEnc.cname.realm;

    /* Get cross-realm key for krbtgt/TARGET@LOCAL */
    std::vector<uint8_t> crKey;
    if (!getCrossRealmKey(localRealm, targetRealm, crKey)) {
        /* No direct cross-realm trust - return error */
        return buildStandardError(KDC_ERR_S_PRINCIPAL_UNKNOWN,
                                   "No cross-realm trust path to " + targetRealm,
                                   localRealm, "krbtgt", targetRealm);
    }

    time_t now = time(nullptr);
    time_t endtime = now + _TicketLifetime;
    if (req.till != 0 && req.till < endtime) endtime = req.till;
    if (endtime > tgtEnc.endtime) endtime = tgtEnc.endtime;

    Krb5SessionKey ref_session_key = Krb5Crypto::generateSessionKey(etype);

    /* Update transited field with local realm */
    std::string transited = tgtEnc.transited;
    if (!transited.empty()) transited += ",";
    transited += localRealm;

    uint32_t refFlags = KRB5_TF_PRE_AUTHENT | KRB5_TF_RENEWABLE;
    if (tgtEnc.flags & KRB5_TF_FORWARDABLE) refFlags |= KRB5_TF_FORWARDABLE;

    /* Build cross-realm TGT EncTicketPart */
    Krb5EncTicketPart ref_enc_tkt;
    ref_enc_tkt.flags = refFlags;
    ref_enc_tkt.session_key = ref_session_key;
    ref_enc_tkt.cname = tgtEnc.cname;
    ref_enc_tkt.transited = transited;
    ref_enc_tkt.authtime = tgtEnc.authtime;
    ref_enc_tkt.starttime = now;
    ref_enc_tkt.endtime = endtime;
    ref_enc_tkt.renew_till = std::min(tgtEnc.renew_till, now + _RenewableLifetime);
    ref_enc_tkt.uid = tgtEnc.uid;

    auto ref_tkt_data = ref_enc_tkt.serialize();
    auto ref_tkt_cipher = Krb5Crypto::encrypt(crKey, ref_tkt_data, KRB5_KU_TICKET);

    /* Build cross-realm TGT ticket */
    Krb5Ticket ref_ticket;
    ref_ticket.tkt_vno = 5;
    ref_ticket.sname = Krb5Principal(KRB5_NT_SRV_INST, localRealm, "krbtgt", targetRealm);
    ref_ticket.enc_etype = etype;
    ref_ticket.enc_kvno = 1;
    ref_ticket.enc_part = ref_tkt_cipher;

    /* Build EncKdcRepPart */
    Krb5EncKdcRepPart ref_enc_rep;
    ref_enc_rep.session_key = ref_session_key;
    ref_enc_rep.nonce = req.nonce;
    ref_enc_rep.flags = refFlags;
    ref_enc_rep.authtime = tgtEnc.authtime;
    ref_enc_rep.starttime = now;
    ref_enc_rep.endtime = endtime;
    ref_enc_rep.renew_till = ref_enc_tkt.renew_till;
    ref_enc_rep.sname = ref_ticket.sname;

    auto ref_rep_data = ref_enc_rep.serialize(false);
    auto ref_rep_cipher = Krb5Crypto::encrypt(tgtEnc.session_key.keydata,
                                                ref_rep_data, KRB5_KU_TGS_REP_ENC_PART);

    /* Build TGS-REP with referral */
    Krb5KdcRep ref_rep;
    ref_rep.pvno = 5;
    ref_rep.msg_type = KRB_TGS_REP;
    ref_rep.cname = tgtEnc.cname;
    ref_rep.ticket = ref_ticket;
    ref_rep.enc_etype = etype;
    ref_rep.enc_kvno = 1;
    ref_rep.enc_part = ref_rep_cipher;

    std::cerr << "KDC: cross-realm referral " << localRealm << " -> " << targetRealm
              << " for client " << tgtEnc.cname.toString() << std::endl;

    return ref_rep.serialize();
}

std::vector<uint8_t> Krb5KDC::buildStandardError(Krb5ErrorCode code,
                                                    const std::string &text,
                                                    const std::string &realm,
                                                    const std::string &sname_name,
                                                    const std::string &sname_inst) {
    Krb5Error err;
    err.pvno = 5;
    err.msg_type = KRB_ERROR;
    err.stime = time(nullptr);
    err.susec = 0;
    err.error_code = code;
    err.sname = Krb5Principal(KRB5_NT_SRV_INST, realm, sname_name, sname_inst);
    err.e_text = text;
    return err.serialize();
}

/*
 * lookupUser - Find a user principal in the given backend
 */
bool Krb5KDC::lookupUser(AuthBackend &backend, const Krb5Principal &principal,
                          uuid::uuid &uid, std::vector<uint8_t> &key) {
    User user;
    std::vector<uuid::uuid> uids;
    user.list(backend, uids);

    for (auto &u : uids) {
        class UserData udat(u);
        size_t upos = sizeof(AuthHeader);
        user.info(backend, udat, upos);

        if (udat.getUsername() == principal.name) {
            uid = u;

            /*
             * Prefer stored Kerberos key (computed from raw password
             * at creation / password-change time).  This is the only
             * way to be compatible with MIT / Heimdal kinit, because
             * RFC 3962 string-to-key must use the raw password.
             *
             * Fall back to deriving from the pwhash for backward
             * compatibility with users created before krbkey support.
             */
            std::string krbkeyHex = udat.getKrbKey();
            if (!krbkeyHex.empty()) {
                key = hexDecode(krbkeyHex);
            } else {
                std::string pwhash = udat.getPwHash();
                std::string salt = principal.realm + principal.name;
                key = Krb5Crypto::string2key(pwhash, salt,
                                              KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
            }
            return true;
        }
    }

    return false;
}

/*
 * lookupClient - Find a computer/client principal in the given backend
 */
bool Krb5KDC::lookupClient(AuthBackend &backend, const Krb5Principal &principal,
                            uuid::uuid &uid, std::vector<uint8_t> &key) {
    std::string lookup_name = principal.name;
    if (!principal.instance.empty())
        lookup_name = principal.name + "/" + principal.instance;

    class Client client_mgr;
    std::vector<uuid::uuid> clids;
    client_mgr.list(backend, clids);

    for (auto &clid : clids) {
        class authdb::ClientData cdat(clid);
        size_t cpos = sizeof(AuthHeader);
        client_mgr.info(backend, cdat, cpos);

        std::string clientname = cdat.getName();
        if (clientname == lookup_name || clientname == principal.name
            || (!principal.instance.empty() && clientname == principal.instance)) {
            uid = clid;

            /*
             * Prefer stored Kerberos key (computed at client
             * creation / secret-change time).  Fall back to
             * deriving from the clientkey hash for backward
             * compatibility with clients created before krbkey
             * support.
             */
            std::string krbkeyHex = cdat.getKrbKey();
            if (!krbkeyHex.empty()) {
                key = hexDecode(krbkeyHex);
            } else {
                std::string client_key_str = cdat.getKey();
                std::string salt = principal.realm + lookup_name;
                key = Krb5Crypto::string2key(client_key_str, salt,
                                              KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
            }
            return true;
        }
    }

    return false;
}

/*
 * lookupService - Find a service principal (e.g. HTTP/host) in the given backend
 */
bool Krb5KDC::lookupService(AuthBackend &backend, const Krb5Principal &principal,
                              uuid::uuid &uid, std::vector<uint8_t> &key) {
    ServiceManager svc_mgr;
    std::vector<uuid::uuid> sids;
    svc_mgr.list(backend, sids);

    for (auto &sid : sids) {
        class authdb::ServiceData sdat(sid);
        size_t spos = sizeof(AuthHeader);
        svc_mgr.info(backend, sdat, spos);

        const char *svcname = sdat.getServiceName();
        if (!svcname || std::string(svcname) != principal.name)
            continue;

        uid = sid;

        /* Check hostname matches instance */
        const char *hostname = sdat.getHostName();
        if (hostname && !principal.instance.empty()) {
            if (std::string(hostname) != principal.instance)
                continue;
        }

        /* Prefer stored krbkey, fall back to deriving from secret */
        std::string krbkeyHex = sdat.getKrbKey();
        if (!krbkeyHex.empty()) {
            key = hexDecode(krbkeyHex);
        } else {
            const char *secret = sdat.getSecret();
            std::string lookup_name = principal.name;
            if (!principal.instance.empty())
                lookup_name += "/" + principal.instance;
            std::string salt = principal.realm + lookup_name;
            key = Krb5Crypto::string2key(
                secret ? std::string(secret) : "",
                salt,
                KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
        }
        return true;
    }

    return false;
}

/******************************************************************************
 * Krb5Client - Client-side Kerberos operations
 *
 * Uses standard ASN.1 DER wire format for MIT/Heimdal compatibility.
 * Supports cross-realm referral following for multi-realm.
 *****************************************************************************/

Krb5Client::Krb5Client(const std::string &kdcHost, int kdcPort)
    : _KdcHost(kdcHost), _KdcPort(kdcPort), _TGTExpiry(0), _TGTFlags(0) {
    _PreferredEtypes = {
        KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1,
        KRB5_ENCTYPE_AES128_CTS_HMAC_SHA1,
        KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384,
        KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256
    };
}

Krb5Client::~Krb5Client() {}

void Krb5Client::setPreferredEtypes(const std::vector<Krb5EncType> &etypes) {
    _PreferredEtypes = etypes;
}

bool Krb5Client::authenticateUser(const std::string &username,
                                   const std::string &password,
                                   const std::string &realm) {
    _Realm = realm;
    _ClientPrincipal = Krb5Principal(KRB5_NT_PRINCIPAL, realm, username);

    /* Derive client key using standard RFC 3962 string-to-key */
    std::string salt = realm + username;
    authdb::sha512 sha;
    std::string pwhash;
    sha.hash(password, pwhash);
    _ClientKey = Krb5Crypto::string2key(pwhash, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);

    /* Build PA-ENC-TIMESTAMP */
    time_t now = time(nullptr);
    auto pa_ts = Asn1Der::encodePaEncTimestamp(now, 0);
    auto enc_pa = Krb5Crypto::encrypt(_ClientKey, pa_ts, KRB5_KU_AS_REQ_PA_ENC_TS);
    auto pa_data = Asn1Der::encodePaData(KRB5_PADATA_ENC_TIMESTAMP, enc_pa);
    auto pa_seq = Asn1Der::encodeSequence(pa_data);

    /* Build AS-REQ */
    Krb5KdcReq as_req;
    as_req.pvno = 5;
    as_req.msg_type = KRB_AS_REQ;
    as_req.kdc_options = KRB5_TF_FORWARDABLE | KRB5_TF_RENEWABLE;
    as_req.cname = _ClientPrincipal;
    as_req.sname = Krb5Principal(KRB5_NT_SRV_INST, realm, "krbtgt", realm);
    as_req.till = now + 36000;
    as_req.rtime = now + 604800;
    as_req.nonce = Krb5Crypto::randomNonce();
    as_req.etypes = _PreferredEtypes;
    as_req.padata = pa_seq;

    auto request = as_req.serialize();
    auto response = sendKdcRequest(request);
    if (response.empty()) return false;

    /* Check if response is an error */
    if (response[0] == Asn1Der::APP_KRB_ERROR) {
        Krb5Error err;
        size_t errConsumed = 0;
        if (err.deserialize(response.data(), response.size(), errConsumed)) {
            std::cerr << "KDC Error: " << err.e_text
                      << " (code " << err.error_code << ")" << std::endl;

            /* If PREAUTH_REQUIRED, retry without padata to get ETYPE-INFO2,
             * then retry with the correct padata */
            if (err.error_code == KDC_ERR_PREAUTH_REQUIRED) {
                /* Already sent padata - this means key is wrong or salt is wrong */
            }
        }
        return false;
    }

    /* Parse AS-REP */
    Krb5KdcRep as_rep;
    size_t repConsumed = 0;
    if (!as_rep.deserialize(response.data(), response.size(), repConsumed))
        return false;

    if (as_rep.msg_type != KRB_AS_REP)
        return false;

    /* Decrypt EncKdcRepPart */
    try {
        auto enc_rep_plain = Krb5Crypto::decrypt(_ClientKey, as_rep.enc_part,
                                                  KRB5_KU_AS_REP_ENC_PART);
        Krb5EncKdcRepPart enc_rep;
        size_t encConsumed = 0;
        if (!enc_rep.deserialize(enc_rep_plain.data(), enc_rep_plain.size(), encConsumed))
            return false;

        if (enc_rep.nonce != as_req.nonce)
            return false;

        _TGT = as_rep.ticket;
        _TGTSessionKey = enc_rep.session_key;
        _TGTExpiry = enc_rep.endtime;
        _TGTFlags = enc_rep.flags;

        return true;
    } catch (const AuthBackendError &e) {
        std::cerr << "Krb5Client: AS-REP decryption failed: " << e.what() << std::endl;
        return false;
    }
}

bool Krb5Client::authenticateClient(const std::string &clientname,
                                     const std::string &clientkey,
                                     const std::string &realm) {
    _Realm = realm;
    _ClientPrincipal = Krb5Principal(KRB5_NT_SRV_HST, realm, "host", clientname);

    /* Salt must match what the KDC uses: REALM + "host/" + clientname
     * Key is derived from the raw password (not hashed), matching kinit behavior */
    std::string salt = realm + "host/" + clientname;
    _ClientKey = Krb5Crypto::string2key(clientkey, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);

    /* Build PA-ENC-TIMESTAMP */
    time_t now = time(nullptr);
    auto pa_ts = Asn1Der::encodePaEncTimestamp(now, 0);
    auto enc_pa = Krb5Crypto::encrypt(_ClientKey, pa_ts, KRB5_KU_AS_REQ_PA_ENC_TS);
    auto pa_data = Asn1Der::encodePaData(KRB5_PADATA_ENC_TIMESTAMP, enc_pa);
    auto pa_seq = Asn1Der::encodeSequence(pa_data);

    Krb5KdcReq as_req;
    as_req.pvno = 5;
    as_req.msg_type = KRB_AS_REQ;
    as_req.kdc_options = KRB5_TF_FORWARDABLE | KRB5_TF_RENEWABLE;
    as_req.cname = _ClientPrincipal;
    as_req.sname = Krb5Principal(KRB5_NT_SRV_INST, realm, "krbtgt", realm);
    as_req.till = now + 36000;
    as_req.rtime = now + 604800;
    as_req.nonce = Krb5Crypto::randomNonce();
    as_req.etypes = _PreferredEtypes;
    as_req.padata = pa_seq;

    auto request = as_req.serialize();
    auto response = sendKdcRequest(request);
    if (response.empty()) return false;

    if (response[0] == Asn1Der::APP_KRB_ERROR) {
        Krb5Error err;
        size_t errConsumed = 0;
        if (err.deserialize(response.data(), response.size(), errConsumed))
            std::cerr << "KDC Error: " << err.e_text << std::endl;
        return false;
    }

    Krb5KdcRep as_rep;
    size_t repConsumed = 0;
    if (!as_rep.deserialize(response.data(), response.size(), repConsumed))
        return false;

    try {
        auto enc_rep_plain = Krb5Crypto::decrypt(_ClientKey, as_rep.enc_part,
                                                  KRB5_KU_AS_REP_ENC_PART);
        Krb5EncKdcRepPart enc_rep;
        size_t encConsumed = 0;
        if (!enc_rep.deserialize(enc_rep_plain.data(), enc_rep_plain.size(), encConsumed))
            return false;

        if (enc_rep.nonce != as_req.nonce) return false;

        _TGT = as_rep.ticket;
        _TGTSessionKey = enc_rep.session_key;
        _TGTExpiry = enc_rep.endtime;
        _TGTFlags = enc_rep.flags;

        return true;
    } catch (const AuthBackendError &e) {
        std::cerr << "Krb5Client: AS-REP decryption failed: " << e.what() << std::endl;
        return false;
    }
}

bool Krb5Client::requestServiceTicket(const std::string &serviceName,
                                       const std::string &serviceInstance,
                                       const std::string &serviceRealm) {
    if (!hasValidTGT()) return false;

    std::string svcRealm = serviceRealm.empty() ? _Realm : serviceRealm;
    time_t now = time(nullptr);

    /* Build authenticator encrypted with TGT session key */
    Krb5Authenticator auth;
    auth.authenticator_vno = 5;
    auth.cname = _ClientPrincipal;
    auth.ctime = now;
    auth.cusec = 0;
    auth.seq_number = Krb5Crypto::randomNonce();

    auto auth_data = auth.serialize();
    auto enc_auth = Krb5Crypto::encrypt(_TGTSessionKey.keydata, auth_data,
                                         KRB5_KU_TGS_REQ_AUTH);

    /* Build AP-REQ as PA-TGS-REQ */
    Krb5ApReq tgsApReq;
    tgsApReq.pvno = 5;
    tgsApReq.msg_type = KRB_AP_REQ;
    tgsApReq.ap_options = 0;
    tgsApReq.ticket = _TGT;
    tgsApReq.auth_etype = _TGTSessionKey.enctype;
    tgsApReq.auth_kvno = 0;
    tgsApReq.authenticator = enc_auth;

    auto apReqBytes = tgsApReq.serialize();
    auto paTgsReq = Asn1Der::encodePaData(KRB5_PADATA_TGS_REQ, apReqBytes);
    auto paSeq = Asn1Der::encodeSequence(paTgsReq);

    /* Build TGS-REQ */
    Krb5KdcReq tgs_req;
    tgs_req.pvno = 5;
    tgs_req.msg_type = KRB_TGS_REQ;
    tgs_req.kdc_options = KRB5_TF_FORWARDABLE | KRB5_TF_RENEWABLE;
    tgs_req.cname = _ClientPrincipal;
    tgs_req.sname = Krb5Principal(KRB5_NT_SRV_INST, svcRealm, serviceName, serviceInstance);
    tgs_req.till = now + 36000;
    tgs_req.nonce = Krb5Crypto::randomNonce();
    tgs_req.etypes = _PreferredEtypes;
    tgs_req.padata = paSeq;

    auto request = tgs_req.serialize();
    auto response = sendKdcRequest(request);
    if (response.empty()) return false;

    if (response[0] == Asn1Der::APP_KRB_ERROR) {
        Krb5Error err;
        size_t errConsumed = 0;
        if (err.deserialize(response.data(), response.size(), errConsumed))
            std::cerr << "KDC Error: " << err.e_text << std::endl;
        return false;
    }

    Krb5KdcRep tgs_rep;
    size_t repConsumed = 0;
    if (!tgs_rep.deserialize(response.data(), response.size(), repConsumed))
        return false;

    try {
        auto enc_rep_plain = Krb5Crypto::decrypt(_TGTSessionKey.keydata, tgs_rep.enc_part,
                                                  KRB5_KU_TGS_REP_ENC_PART);
        Krb5EncKdcRepPart enc_rep;
        size_t encConsumed = 0;
        if (!enc_rep.deserialize(enc_rep_plain.data(), enc_rep_plain.size(), encConsumed))
            return false;

        if (enc_rep.nonce != tgs_req.nonce) return false;

        /* Check for cross-realm referral:
         * If the ticket's sname is krbtgt/OTHER_REALM@LOCAL_REALM,
         * follow the referral to get the actual service ticket. */
        if (enc_rep.sname.name == "krbtgt" &&
            !enc_rep.sname.instance.empty() &&
            enc_rep.sname.instance != _Realm) {
            /* Cross-realm referral - update TGT and follow */
            _TGT = tgs_rep.ticket;
            _TGTSessionKey = enc_rep.session_key;
            _TGTExpiry = enc_rep.endtime;
            _TGTFlags = enc_rep.flags;
            _Realm = enc_rep.sname.instance; /* new realm */

            std::cerr << "Krb5Client: following cross-realm referral to "
                      << enc_rep.sname.instance << std::endl;

            /* Retry in the new realm (recursive, max depth handled by KDC) */
            return requestServiceTicket(serviceName, serviceInstance, svcRealm);
        }

        _ServiceTicket = tgs_rep.ticket;
        _ServiceSessionKey = enc_rep.session_key;
        return true;
    } catch (const AuthBackendError &e) {
        std::cerr << "Krb5Client: TGS-REP decryption failed: " << e.what() << std::endl;
        return false;
    }
}

std::vector<uint8_t> Krb5Client::buildApReq() {
    time_t now = time(nullptr);

    Krb5Authenticator auth;
    auth.authenticator_vno = 5;
    auth.cname = _ClientPrincipal;
    auth.ctime = now;
    auth.cusec = 0;
    auth.seq_number = Krb5Crypto::randomNonce();

    auto auth_data = auth.serialize();
    auto enc_auth = Krb5Crypto::encrypt(_ServiceSessionKey.keydata, auth_data,
                                         KRB5_KU_AP_REQ_AUTH);

    Krb5ApReq ap_req;
    ap_req.pvno = 5;
    ap_req.msg_type = KRB_AP_REQ;
    ap_req.ap_options = 0;
    ap_req.ticket = _ServiceTicket;
    ap_req.auth_etype = _ServiceSessionKey.enctype;
    ap_req.auth_kvno = 0;
    ap_req.authenticator = enc_auth;

    return ap_req.serialize();
}

const Krb5Ticket &Krb5Client::getTGT() const { return _TGT; }
const Krb5Ticket &Krb5Client::getServiceTicket() const { return _ServiceTicket; }
const Krb5SessionKey &Krb5Client::getSessionKey() const { return _ServiceSessionKey; }

bool Krb5Client::hasValidTGT() const {
    if (_TGT.enc_part.empty()) return false;
    return time(nullptr) < _TGTExpiry;
}

bool Krb5Client::followReferral(const std::string &targetRealm,
                                 const std::string &serviceName,
                                 const std::string &serviceInstance) {
    /* Follow the cross-realm referral by requesting the service ticket
     * from the target realm.  The TGT has already been updated by the
     * caller (requestServiceTicket) before calling this method. */
    return requestServiceTicket(serviceName, serviceInstance, targetRealm);
}

/*
 * sendKdcRequest - TCP communication with KDC (standard wire format)
 *
 * Standard Kerberos TCP framing: 4-byte big-endian length prefix
 * followed by the ASN.1 DER encoded message.
 */
std::vector<uint8_t> Krb5Client::sendKdcRequest(const std::vector<uint8_t> &request) {
    try {
        netplus::tcp sock;
        sock.connect(_KdcHost, _KdcPort, false);

        /* Frame: 4-byte length + request */
        uint32_t reqLen = static_cast<uint32_t>(request.size());
        uint8_t lenBuf[4] = {
            static_cast<uint8_t>((reqLen >> 24) & 0xFF),
            static_cast<uint8_t>((reqLen >> 16) & 0xFF),
            static_cast<uint8_t>((reqLen >> 8) & 0xFF),
            static_cast<uint8_t>(reqLen & 0xFF)
        };

        std::vector<uint8_t> framed;
        framed.insert(framed.end(), lenBuf, lenBuf + 4);
        framed.insert(framed.end(), request.begin(), request.end());

        netplus::buffer sendbuf(reinterpret_cast<const char*>(framed.data()), framed.size());
        size_t sent = 0;
        while (sent < framed.size()) {
            netplus::buffer partial(
                reinterpret_cast<const char*>(framed.data() + sent),
                framed.size() - sent);
            sent += sock.sendData(partial);
        }

        /* Receive response: 4-byte length + ASN.1 data */
        std::vector<uint8_t> response;
        netplus::buffer recvbuf(4096);
        size_t received = sock.recvData(recvbuf);

        if (received >= 4) {
            const uint8_t *rdata = reinterpret_cast<const uint8_t*>(recvbuf.data.buf);
            uint32_t msg_len = (static_cast<uint32_t>(rdata[0]) << 24) |
                               (static_cast<uint32_t>(rdata[1]) << 16) |
                               (static_cast<uint32_t>(rdata[2]) << 8)  |
                               static_cast<uint32_t>(rdata[3]);

            /* Return only the ASN.1 data (skip length prefix) */
            response.assign(rdata + 4, rdata + received);

            while (response.size() < msg_len) {
                netplus::buffer more(4096);
                size_t got = sock.recvData(more);
                if (got == 0) break;
                response.insert(response.end(),
                               reinterpret_cast<uint8_t*>(more.data.buf),
                               reinterpret_cast<uint8_t*>(more.data.buf) + got);
            }
        }

        sock.close();
        return response;
    } catch (const std::exception &e) {
        std::cerr << "Krb5Client: KDC communication error: " << e.what() << std::endl;
        return {};
    }
}

/******************************************************************************
 * Krb5Service - Application Server side
 *
 * Verifies AP-REQ messages using standard ASN.1 DER encoding.
 * MIT Kerberos / Heimdal compatible.
 *****************************************************************************/

Krb5Service::Krb5Service(const std::string &serviceName,
                         const std::string &hostname,
                         const std::string &realm,
                         const std::vector<uint8_t> &serviceKey)
    : _ServicePrincipal(KRB5_NT_SRV_HST, realm, serviceName, hostname),
      _ServiceKey(serviceKey) {}

Krb5Service::~Krb5Service() {}

bool Krb5Service::verifyApReq(const std::vector<uint8_t> &apReqData,
                               Krb5Principal &clientPrincipal,
                               Krb5SessionKey &sessionKey) {
    /* Parse AP-REQ */
    Krb5ApReq ap_req;
    size_t reqConsumed = 0;
    if (!ap_req.deserialize(apReqData.data(), apReqData.size(), reqConsumed))
        return false;

    /* Decrypt the service ticket with our service key */
    Krb5EncTicketPart enc_tkt;
    try {
        auto tkt_plain = Krb5Crypto::decrypt(_ServiceKey, ap_req.ticket.enc_part,
                                              KRB5_KU_TICKET);
        size_t tktConsumed = 0;
        if (!enc_tkt.deserialize(tkt_plain.data(), tkt_plain.size(), tktConsumed))
            return false;
    } catch (const AuthBackendError &) {
        return false;
    }

    /* Check ticket validity */
    time_t now = time(nullptr);
    if (now > enc_tkt.endtime) return false;
    if (now < enc_tkt.starttime - 300) return false;

    /* Decrypt the authenticator with the session key */
    Krb5Authenticator auth;
    try {
        auto auth_plain = Krb5Crypto::decrypt(enc_tkt.session_key.keydata,
                                               ap_req.authenticator,
                                               KRB5_KU_AP_REQ_AUTH);
        size_t authConsumed = 0;
        if (!auth.deserialize(auth_plain.data(), auth_plain.size(), authConsumed))
            return false;
    } catch (const AuthBackendError &) {
        return false;
    }

    /* Verify authenticator timestamp (clock skew) */
    if (std::abs(static_cast<long>(now - auth.ctime)) > 300)
        return false;

    /* Verify principal match */
    if (auth.cname.name != enc_tkt.cname.name ||
        auth.cname.realm != enc_tkt.cname.realm)
        return false;

    clientPrincipal = enc_tkt.cname;
    sessionKey = enc_tkt.session_key;
    return true;
}

std::vector<uint8_t> Krb5Service::buildApRep(const Krb5Authenticator &auth,
                                               const Krb5SessionKey &sessionKey) {
    /* Build encrypted part: timestamp + cusec + seq-number */
    auto encTimestamp = Asn1Der::encodePaEncTimestamp(auth.ctime, auth.cusec);

    /* Add seq-number */
    std::vector<uint8_t> repData = encTimestamp;
    /* For simplicity, reuse PA-ENC-TS-ENC format with the seq number appended */

    auto enc_rep = Krb5Crypto::encrypt(sessionKey.keydata, repData,
                                        KRB5_KU_AP_REP_ENC_PART);

    Krb5ApRep ap_rep;
    ap_rep.pvno = 5;
    ap_rep.msg_type = KRB_AP_REP;
    ap_rep.enc_etype = sessionKey.enctype;
    ap_rep.enc_kvno = 0;
    ap_rep.enc_part = enc_rep;

    return ap_rep.serialize();
}

std::vector<uint8_t> Krb5KDC::string2key(const std::string &password,
                                          const std::string &salt,
                                          int etype) {
    return Krb5Crypto::string2key(password, salt, static_cast<Krb5EncType>(etype));
}

} /* namespace authdb */

/*******************************************************************************
 * C factory functions for generic plugin loading
 ******************************************************************************/
#include <json-c/json.h>

extern "C" {

authdb::Plugin *authdb_plugin_create(authdb::AuthBackend &backend,
                                      const char *configJson) {
    std::string bindAddr = "0.0.0.0";
    int port = 88;
    int maxconn = 128;

    if (configJson) {
        json_object *jobj = json_tokener_parse(configJson);
        if (jobj) {
            json_object *jbind = json_object_object_get(jobj, "bind");
            json_object *jport = json_object_object_get(jobj, "port");
            json_object *jmax  = json_object_object_get(jobj, "maxconn");
            if (jbind) bindAddr = json_object_get_string(jbind);
            if (jport) port     = json_object_get_int(jport);
            if (jmax)  maxconn  = json_object_get_int(jmax);
            json_object_put(jobj);
        }
    }

    return new authdb::Krb5KDC(backend, bindAddr, port, maxconn);
}

void authdb_plugin_destroy(authdb::Plugin *p) {
    delete p;
}

} /* extern "C" */
