Loading plugin/ad/ad.cpp +6 −1 Changes for plugin/ad/ad.cpp: 6 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -1533,11 +1533,16 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content, std::vector<uint8_t> krb5reply; if (!res.apRep.empty()) { /* RFC 1964 framing: 0x60, krb5 OID, tok id 02 00, AP-REP */ if (res.rawApRepToken) { /* TEMPORARY interop diagnosis */ krb5reply = {0x02, 0x00}; krb5reply.insert(krb5reply.end(), res.apRep.begin(), res.apRep.end()); } else { krb5reply = gss::wrapInitialToken(res.apRep, 0x0200); } } std::vector<uint8_t> creds; if (spnego) { std::vector<uint8_t> krb5oid(gss::KRB5_OID, gss::KRB5_OID + 11); std::vector<uint8_t> krb5oid = gss::firstMechOid(session.gssMechTypes); std::vector<uint8_t> mic = gss::getMic(session.gssKey, session.gssEtype, true, session.sendSeq++, session.gssMechTypes); creds = gss::buildSpnegoResp(gss::ACCEPT_COMPLETED, &krb5oid, krb5reply, mic); Loading plugin/krb5/krb5.cpp +28 −2 Changes for plugin/krb5/krb5.cpp: 28 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <atomic> #include <iostream> #include <cstring> #include <cstdlib> Loading Loading @@ -3864,15 +3865,40 @@ bool Krb5KDC::gssAccept(const std::vector<uint8_t> &apReqData, GssAcceptResult & /* KRB5_AP_OPTS_MUTUAL_REQUIRED = 0x20000000: the AP-REP enc-part is * encrypted with the TICKET session key (not the subkey). */ /* TEMPORARY Windows interop diagnosis: rotate through AP-REP variants * (FENRIRDS_APREP_VARIANT=0..3 pins one). A: framed, EncAPRepPart, own * seq; B: framed, EncAPRepPart, seq 0; C: unframed, EncAPRepPart, seq 0; * D: the pre-2026-10-30 reply (unframed, PA-ENC-TS enc part, seq 0). */ static std::atomic<unsigned> s_variant{0}; int variant; if (const char *pin = getenv("FENRIRDS_APREP_VARIANT")) variant = std::atoi(pin) & 3; else variant = 0; (void)s_variant; out.apRepVariant = variant; out.rawApRepToken = variant >= 2; /* our initial GSS sequence number, announced in the AP-REP */ out.acceptorSeq = Krb5Crypto::randomNonce() & 0x3fffffff; out.acceptorSeq = variant == 0 ? (Krb5Crypto::randomNonce() & 0x3fffffff) : 0; if (ap.ap_options & 0x20000000) { Krb5Service svc("", "", ap.ticket.sname.realm, svcKey); if (variant == 3) { auto legacy = Asn1Der::encodePaEncTimestamp(auth.ctime, auth.cusec); Krb5ApRep ap_rep; ap_rep.pvno = 5; ap_rep.msg_type = KRB_AP_REP; ap_rep.enc_etype = et.session_key.enctype; ap_rep.enc_kvno = 0; ap_rep.enc_part = Krb5Crypto::encrypt(et.session_key.keydata, legacy, KRB5_KU_AP_REP_ENC_PART); out.apRep = ap_rep.serialize(); } else { out.apRep = svc.buildApRep(auth, et.session_key, out.acceptorSeq); } } out.gssFlags = auth.has_gss_cksum ? auth.gss_flags : 0; out.initiatorSeq = auth.seq_number; std::cerr << "[KDC] gssAccept " << out.clientPrincipal << " subkey=" << auth.has_subkey std::cerr << "[KDC] gssAccept " << out.clientPrincipal << " variant=" << "ABCD"[out.apRepVariant] << " subkey=" << auth.has_subkey << " flags=0x" << std::hex << out.gssFlags << " initSeq=0x" << out.initiatorSeq << " accSeq=0x" << out.acceptorSeq << std::dec << " mutual=" << !out.apRep.empty() << std::endl; return true; Loading plugin/smb/smb.cpp +32 −4 Changes for plugin/smb/smb.cpp: 32 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -264,6 +264,7 @@ void SmbServer::ConnectEvent(netplus::con &c, const int, ULONG_PTR) { std::lock_guard<std::mutex> l(_Lock); _Conns[&c] = Conn(); } void SmbServer::DisconnectEvent(netplus::con &c, const int, ULONG_PTR) { std::cerr << "[SMB] disconnect" << std::endl; /* TEMPORARY interop diagnosis */ std::lock_guard<std::mutex> l(_Lock); auto it = _Conns.find(&c); if (it != _Conns.end()) { Loading Loading @@ -357,6 +358,25 @@ std::vector<uint8_t> SmbServer::handleCompound(Conn &c, const std::vector<uint8_ off += next; } /* TEMPORARY interop diagnosis: every request and its reply status */ { size_t o = 0; size_t ri = 0; while (o + 64 <= msg.size()) { uint16_t cmd = uint16_t(msg[o + 12] | msg[o + 13] << 8); uint64_t sid = 0; for (int i = 0; i < 8; i++) sid |= uint64_t(msg[o + 40 + i]) << (8 * i); uint32_t st = ri < replies.size() ? rd32(replies[ri].pdu.data() + 8) : 0xffffffff; std::cerr << "[SMB] req cmd=" << cmd << " sid=0x" << std::hex << sid << " flags=0x" << rd32(msg.data() + o + 16) << " -> status=0x" << st << std::dec << (ri < replies.size() && !replies[ri].signKey.empty() ? " signed" : "") << std::endl; ++ri; uint32_t next = rd32(msg.data() + o + 20); if (!next) break; o += next; } } std::vector<uint8_t> out; for (size_t i = 0; i < replies.size(); ++i) { auto &pdu = replies[i].pdu; Loading Loading @@ -715,17 +735,24 @@ SmbServer::Reply SmbServer::sessionSetup(Conn &c, const smb::Smb2Header &req, GssAcceptResult res; if (!g_KdcPlugin->gssAccept(inner, res)) return fail("Kerberos AP-REQ not accepted"); std::cerr << "[SMB] Kerberos session " << res.clientPrincipal << std::endl; std::cerr << "[SMB] Kerberos session " << res.clientPrincipal << " variant=" << "ABCD"[res.apRepVariant] << " sid=0x" << std::hex << sid << std::dec << std::endl; s.authenticated = true; setPrincipal(res.clientPrincipal); s.sessionKey = res.sessionKey; std::vector<uint8_t> krb5reply; /* the reply mech token: RFC 1964 framing (0x60, krb5 OID, tok id 02 00) */ if (!res.apRep.empty()) if (!res.apRep.empty()) { if (res.rawApRepToken) { /* TEMPORARY interop diagnosis */ krb5reply = {0x02, 0x00}; krb5reply.insert(krb5reply.end(), res.apRep.begin(), res.apRep.end()); } else { krb5reply = gss::wrapInitialToken(res.apRep, 0x0200); } } std::vector<uint8_t> outTok; if (spnego) { std::vector<uint8_t> oid(gss::KRB5_OID, gss::KRB5_OID+11); std::vector<uint8_t> oid = gss::firstMechOid(mechTypes); auto m = gss::getMic(res.sessionKey, res.sessionEtype, true, res.acceptorSeq, mechTypes); outTok = gss::buildSpnegoResp(gss::ACCEPT_COMPLETED, &oid, krb5reply, m); } else { Loading Loading @@ -760,7 +787,8 @@ std::vector<uint8_t> SmbServer::treeConnect(Session &s, const smb::Smb2Header &r return errorResponse(req, ST_ACCESS_DENIED); uint32_t tid = s.nextTree++; s.trees[tid] = t; std::cerr << "[SMB] TREE_CONNECT '" << path << "' tid=" << tid << " user=" << s.principal << std::endl; std::cerr << "[SMB] TREE_CONNECT '" << path << "' tid=" << tid << " user=" << s.principal << " sid=0x" << std::hex << s.id << std::dec << std::endl; smb::Smb2Header h; h.command = req.command; h.messageId = req.messageId; h.sessionId = s.id; Loading src/crypto/gss.cpp +15 −0 Changes for src/crypto/gss.cpp: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -248,6 +248,21 @@ namespace { } } std::vector<uint8_t> firstMechOid(const std::vector<uint8_t> &m) { std::vector<uint8_t> krb5(KRB5_OID, KRB5_OID + sizeof(KRB5_OID)); if (m.size() < 4 || m[0] != 0x30) return krb5; size_t off = 1, len = 0; if (!readLen(m.data(), m.size(), off, len) || off + len > m.size()) return krb5; if (off + 2 > m.size() || m[off] != 0x06) return krb5; size_t oidLen = m[off + 1]; if (oidLen > 0x7f || off + 2 + oidLen > m.size()) return krb5; return std::vector<uint8_t>(m.begin() + off, m.begin() + off + 2 + oidLen); } bool parseSpnegoInit(const std::vector<uint8_t> &tok, std::vector<uint8_t> &mechToken, std::vector<uint8_t> &mechTypesDer) { Loading src/crypto/gss.h +7 −0 Changes for src/crypto/gss.h: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -59,6 +59,13 @@ namespace fenrirds { bool parseSpnegoInit(const std::vector<uint8_t> &tok, std::vector<uint8_t> &mechToken, std::vector<uint8_t> &mechTypesDer); /* the first mechanism OID (DER TLV 06 len ...) of a mechTypes * list as parseSpnegoInit returns it: the mech the client's * optimistic token belongs to. The reply must name exactly this * OID -- Windows offers the MS Kerberos OID 1.2.840.48018.1.2.2 * first and drops the context if we answer with the standard * one. Falls back to the krb5 OID. */ std::vector<uint8_t> firstMechOid(const std::vector<uint8_t> &mechTypesDer); /* parse a NegTokenResp (continuation): its responseToken */ bool parseSpnegoResp(const std::vector<uint8_t> &tok, std::vector<uint8_t> &responseToken, Loading Loading
plugin/ad/ad.cpp +6 −1 Changes for plugin/ad/ad.cpp: 6 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -1533,11 +1533,16 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content, std::vector<uint8_t> krb5reply; if (!res.apRep.empty()) { /* RFC 1964 framing: 0x60, krb5 OID, tok id 02 00, AP-REP */ if (res.rawApRepToken) { /* TEMPORARY interop diagnosis */ krb5reply = {0x02, 0x00}; krb5reply.insert(krb5reply.end(), res.apRep.begin(), res.apRep.end()); } else { krb5reply = gss::wrapInitialToken(res.apRep, 0x0200); } } std::vector<uint8_t> creds; if (spnego) { std::vector<uint8_t> krb5oid(gss::KRB5_OID, gss::KRB5_OID + 11); std::vector<uint8_t> krb5oid = gss::firstMechOid(session.gssMechTypes); std::vector<uint8_t> mic = gss::getMic(session.gssKey, session.gssEtype, true, session.sendSeq++, session.gssMechTypes); creds = gss::buildSpnegoResp(gss::ACCEPT_COMPLETED, &krb5oid, krb5reply, mic); Loading
plugin/krb5/krb5.cpp +28 −2 Changes for plugin/krb5/krb5.cpp: 28 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -25,6 +25,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <atomic> #include <iostream> #include <cstring> #include <cstdlib> Loading Loading @@ -3864,15 +3865,40 @@ bool Krb5KDC::gssAccept(const std::vector<uint8_t> &apReqData, GssAcceptResult & /* KRB5_AP_OPTS_MUTUAL_REQUIRED = 0x20000000: the AP-REP enc-part is * encrypted with the TICKET session key (not the subkey). */ /* TEMPORARY Windows interop diagnosis: rotate through AP-REP variants * (FENRIRDS_APREP_VARIANT=0..3 pins one). A: framed, EncAPRepPart, own * seq; B: framed, EncAPRepPart, seq 0; C: unframed, EncAPRepPart, seq 0; * D: the pre-2026-10-30 reply (unframed, PA-ENC-TS enc part, seq 0). */ static std::atomic<unsigned> s_variant{0}; int variant; if (const char *pin = getenv("FENRIRDS_APREP_VARIANT")) variant = std::atoi(pin) & 3; else variant = 0; (void)s_variant; out.apRepVariant = variant; out.rawApRepToken = variant >= 2; /* our initial GSS sequence number, announced in the AP-REP */ out.acceptorSeq = Krb5Crypto::randomNonce() & 0x3fffffff; out.acceptorSeq = variant == 0 ? (Krb5Crypto::randomNonce() & 0x3fffffff) : 0; if (ap.ap_options & 0x20000000) { Krb5Service svc("", "", ap.ticket.sname.realm, svcKey); if (variant == 3) { auto legacy = Asn1Der::encodePaEncTimestamp(auth.ctime, auth.cusec); Krb5ApRep ap_rep; ap_rep.pvno = 5; ap_rep.msg_type = KRB_AP_REP; ap_rep.enc_etype = et.session_key.enctype; ap_rep.enc_kvno = 0; ap_rep.enc_part = Krb5Crypto::encrypt(et.session_key.keydata, legacy, KRB5_KU_AP_REP_ENC_PART); out.apRep = ap_rep.serialize(); } else { out.apRep = svc.buildApRep(auth, et.session_key, out.acceptorSeq); } } out.gssFlags = auth.has_gss_cksum ? auth.gss_flags : 0; out.initiatorSeq = auth.seq_number; std::cerr << "[KDC] gssAccept " << out.clientPrincipal << " subkey=" << auth.has_subkey std::cerr << "[KDC] gssAccept " << out.clientPrincipal << " variant=" << "ABCD"[out.apRepVariant] << " subkey=" << auth.has_subkey << " flags=0x" << std::hex << out.gssFlags << " initSeq=0x" << out.initiatorSeq << " accSeq=0x" << out.acceptorSeq << std::dec << " mutual=" << !out.apRep.empty() << std::endl; return true; Loading
plugin/smb/smb.cpp +32 −4 Changes for plugin/smb/smb.cpp: 32 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -264,6 +264,7 @@ void SmbServer::ConnectEvent(netplus::con &c, const int, ULONG_PTR) { std::lock_guard<std::mutex> l(_Lock); _Conns[&c] = Conn(); } void SmbServer::DisconnectEvent(netplus::con &c, const int, ULONG_PTR) { std::cerr << "[SMB] disconnect" << std::endl; /* TEMPORARY interop diagnosis */ std::lock_guard<std::mutex> l(_Lock); auto it = _Conns.find(&c); if (it != _Conns.end()) { Loading Loading @@ -357,6 +358,25 @@ std::vector<uint8_t> SmbServer::handleCompound(Conn &c, const std::vector<uint8_ off += next; } /* TEMPORARY interop diagnosis: every request and its reply status */ { size_t o = 0; size_t ri = 0; while (o + 64 <= msg.size()) { uint16_t cmd = uint16_t(msg[o + 12] | msg[o + 13] << 8); uint64_t sid = 0; for (int i = 0; i < 8; i++) sid |= uint64_t(msg[o + 40 + i]) << (8 * i); uint32_t st = ri < replies.size() ? rd32(replies[ri].pdu.data() + 8) : 0xffffffff; std::cerr << "[SMB] req cmd=" << cmd << " sid=0x" << std::hex << sid << " flags=0x" << rd32(msg.data() + o + 16) << " -> status=0x" << st << std::dec << (ri < replies.size() && !replies[ri].signKey.empty() ? " signed" : "") << std::endl; ++ri; uint32_t next = rd32(msg.data() + o + 20); if (!next) break; o += next; } } std::vector<uint8_t> out; for (size_t i = 0; i < replies.size(); ++i) { auto &pdu = replies[i].pdu; Loading Loading @@ -715,17 +735,24 @@ SmbServer::Reply SmbServer::sessionSetup(Conn &c, const smb::Smb2Header &req, GssAcceptResult res; if (!g_KdcPlugin->gssAccept(inner, res)) return fail("Kerberos AP-REQ not accepted"); std::cerr << "[SMB] Kerberos session " << res.clientPrincipal << std::endl; std::cerr << "[SMB] Kerberos session " << res.clientPrincipal << " variant=" << "ABCD"[res.apRepVariant] << " sid=0x" << std::hex << sid << std::dec << std::endl; s.authenticated = true; setPrincipal(res.clientPrincipal); s.sessionKey = res.sessionKey; std::vector<uint8_t> krb5reply; /* the reply mech token: RFC 1964 framing (0x60, krb5 OID, tok id 02 00) */ if (!res.apRep.empty()) if (!res.apRep.empty()) { if (res.rawApRepToken) { /* TEMPORARY interop diagnosis */ krb5reply = {0x02, 0x00}; krb5reply.insert(krb5reply.end(), res.apRep.begin(), res.apRep.end()); } else { krb5reply = gss::wrapInitialToken(res.apRep, 0x0200); } } std::vector<uint8_t> outTok; if (spnego) { std::vector<uint8_t> oid(gss::KRB5_OID, gss::KRB5_OID+11); std::vector<uint8_t> oid = gss::firstMechOid(mechTypes); auto m = gss::getMic(res.sessionKey, res.sessionEtype, true, res.acceptorSeq, mechTypes); outTok = gss::buildSpnegoResp(gss::ACCEPT_COMPLETED, &oid, krb5reply, m); } else { Loading Loading @@ -760,7 +787,8 @@ std::vector<uint8_t> SmbServer::treeConnect(Session &s, const smb::Smb2Header &r return errorResponse(req, ST_ACCESS_DENIED); uint32_t tid = s.nextTree++; s.trees[tid] = t; std::cerr << "[SMB] TREE_CONNECT '" << path << "' tid=" << tid << " user=" << s.principal << std::endl; std::cerr << "[SMB] TREE_CONNECT '" << path << "' tid=" << tid << " user=" << s.principal << " sid=0x" << std::hex << s.id << std::dec << std::endl; smb::Smb2Header h; h.command = req.command; h.messageId = req.messageId; h.sessionId = s.id; Loading
src/crypto/gss.cpp +15 −0 Changes for src/crypto/gss.cpp: 15 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -248,6 +248,21 @@ namespace { } } std::vector<uint8_t> firstMechOid(const std::vector<uint8_t> &m) { std::vector<uint8_t> krb5(KRB5_OID, KRB5_OID + sizeof(KRB5_OID)); if (m.size() < 4 || m[0] != 0x30) return krb5; size_t off = 1, len = 0; if (!readLen(m.data(), m.size(), off, len) || off + len > m.size()) return krb5; if (off + 2 > m.size() || m[off] != 0x06) return krb5; size_t oidLen = m[off + 1]; if (oidLen > 0x7f || off + 2 + oidLen > m.size()) return krb5; return std::vector<uint8_t>(m.begin() + off, m.begin() + off + 2 + oidLen); } bool parseSpnegoInit(const std::vector<uint8_t> &tok, std::vector<uint8_t> &mechToken, std::vector<uint8_t> &mechTypesDer) { Loading
src/crypto/gss.h +7 −0 Changes for src/crypto/gss.h: 7 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -59,6 +59,13 @@ namespace fenrirds { bool parseSpnegoInit(const std::vector<uint8_t> &tok, std::vector<uint8_t> &mechToken, std::vector<uint8_t> &mechTypesDer); /* the first mechanism OID (DER TLV 06 len ...) of a mechTypes * list as parseSpnegoInit returns it: the mech the client's * optimistic token belongs to. The reply must name exactly this * OID -- Windows offers the MS Kerberos OID 1.2.840.48018.1.2.2 * first and drops the context if we answer with the standard * one. Falls back to the krb5 OID. */ std::vector<uint8_t> firstMechOid(const std::vector<uint8_t> &mechTypesDer); /* parse a NegTokenResp (continuation): its responseToken */ bool parseSpnegoResp(const std::vector<uint8_t> &tok, std::vector<uint8_t> &responseToken, Loading