Commit 4dda1c69 authored by jan.koester's avatar jan.koester
Browse files

test

parent 12e2dc4c
Loading
Loading
Loading
Loading
Loading
+1 −1
Changes for docs/ROADMAP.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -59,7 +59,7 @@ The directory layer maps them onto AD objects and adds generic objects:
|---|---|---|
| 0 | Rename authdb -> FenrirDirectoryServer (`fenrirds`) | done |
| 1 | Core lib + crypto (MD4/MD5/HMAC-MD5/RC4/AES-CMAC/CFB8/SP800-108) + dsdb | |
| 2 | LDAP: all operations, controls (paged, SD flags, ...), rootDSE, schema entry, SASL GSSAPI/GSS-SPNEGO (Kerberos+NTLM, sign/seal), LDAPS, GC port, CLDAP netlogon ping | bind(simple+SASL GSSAPI)/search/add/modify/delete/modrdn + rootDSE done & wire-tested; SASL GSSAPI bind works end to end (AP-REQ via KDC acceptor, mutual AP-REP, CFX wrap/unwrap sec-layer). TODO: GSS-SPNEGO wrap, sign/seal security layer (SSF>0), controls (paged), schema subentry, LDAPS, GC/CLDAP |
| 2 | LDAP: all operations, controls (paged, SD flags, ...), rootDSE, schema entry, SASL GSSAPI/GSS-SPNEGO (Kerberos+NTLM, sign/seal), LDAPS, GC port, CLDAP netlogon ping | bind(simple+SASL GSSAPI)/search/add/modify/delete/modrdn + rootDSE done & wire-tested; SASL GSSAPI(raw)+GSS-SPNEGO binds, sign transport (SSF=1), CLDAP netlogon done. kinit/kvno/CLDAP interop-proven vs MIT+Samba. TODO: validate GSS-SPNEGO+sign vs Samba ldbsearch (harness flaky this round), seal(SSF=2), paged-results, schema subentry, LDAPS, GC port |
| 3 | LDIF import/export (RFC 2849): library, `fenrirdsctl`, HTTP API, admin UI | |
| 4 | DNS server: AD zones and SRV records, dynamic update (unsigned + GSS-TSIG), forwarding | UDP+TCP server, SRV/A/NS/SOA for domain + _msdcs, generated from directory + configured DC host/IP; wire-tested (dig-style). TODO: directory-backed dnsNode records, dynamic update (RFC2136 + GSS-TSIG), forwarding, AAAA, reverse zones |
| 5 | Kerberos for AD: PAC (logon info, UPN/DNS info, signatures), lookups by sAMAccountName/SPN/UPN, AD salts, RC4-HMAC, kpasswd | KDC on dsdb::Directory (lookups SPN/sAMAccountName($)/UPN, keys from Secrets, krbtgt=TGS key, AD salts). PAC (logon info NDR, client info, UPN/DNS, server+KDC HMAC-SHA1-96 signatures, S-1-22-3 UUID SID in extra SIDs) in TGT + service ticket. AS+TGS wire-tested end to end. TODO: PAC verify on TGS, RC4-HMAC preauth, kpasswd, S4U |
+93 −11
Changes for plugin/ad/ad.cpp: 93 added lines, 11 removed lines.
Original line number Diff line number Diff line
@@ -497,6 +497,59 @@ void ActiveDirectory::RequestEvent(netplus::con &curcon, const int, ULONG_PTR) {
    if (!session)
        return;

    namespace gss = fenrirds::crypto::gss;

    /* With a SASL security layer, each message is a 4-byte-length-prefixed GSS
     * token wrapping the LDAP PDU; responses are wrapped the same way. */
    if (session->ssf > 0) {
        size_t consumed = 0;
        while (true) {
            size_t avail = curcon.RecvData.size() - consumed;
            if (avail < 4)
                break;
            const uint8_t *p = reinterpret_cast<const uint8_t*>(curcon.RecvData.data()) + consumed;
            uint32_t blen = uint32_t(p[0]) << 24 | uint32_t(p[1]) << 16 | uint32_t(p[2]) << 8 | p[3];
            if (avail - 4 < blen)
                break;
            std::vector<uint8_t> token(p + 4, p + 4 + blen);
            consumed += 4 + blen;

            fenrirds::crypto::Bytes pdu;
            if (!gss::unwrap(session->gssKey, session->gssEtype, false, token, pdu)) {
                /* integrity failure: drop the connection */
                curcon.RecvData.clear();
                return;
            }
            session->recvSeq++;

            /* process the LDAP PDU(s) inside */
            std::vector<uint8_t> responses;
            size_t po = 0;
            while (po < pdu.size()) {
                size_t o = po;
                uint8_t t; const uint8_t *mc; size_t ml;
                if (!LdapBer::parseTLV(pdu.data(), pdu.size(), o, t, mc, ml) || t != LdapBer::TAG_SEQUENCE)
                    break;
                auto r = handleMessage(mc, ml, *session);
                responses.insert(responses.end(), r.begin(), r.end());
                po = o;
            }
            if (!responses.empty()) {
                fenrirds::crypto::Bytes resp(responses.begin(), responses.end());
                auto wrapped = gss::wrap(session->gssKey, session->gssEtype, true, session->sendSeq++, resp);
                uint32_t wl = uint32_t(wrapped.size());
                curcon.SendData.push_back(char(wl >> 24));
                curcon.SendData.push_back(char(wl >> 16));
                curcon.SendData.push_back(char(wl >> 8));
                curcon.SendData.push_back(char(wl));
                curcon.SendData.insert(curcon.SendData.end(), wrapped.begin(), wrapped.end());
            }
        }
        if (consumed > 0)
            curcon.RecvData.erase(curcon.RecvData.begin(), curcon.RecvData.begin() + consumed);
        return;
    }

    const uint8_t *raw = reinterpret_cast<const uint8_t*>(curcon.RecvData.data());
    size_t rawLen = curcon.RecvData.size();
    size_t consumed = 0;
@@ -626,7 +679,10 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content,
        };
        const int SASL_BIND_IN_PROGRESS = 14;

        if (toLower(mechanism) != "gssapi") {
        namespace gss = fenrirds::crypto::gss;
        std::string mech = toLower(mechanism);
        bool spnego = (mech == "gss-spnego");
        if (mech != "gssapi" && !spnego) {
            session.gssState = LdapSession::GssNone;
            return bindResp(LDAP_AUTH_METHOD_NOT_SUPPORTED, nullptr);
        }
@@ -634,10 +690,20 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content,
            return bindResp(LDAP_AUTH_METHOD_NOT_SUPPORTED, nullptr);

        if (session.gssState == LdapSession::GssNone) {
            uint16_t tokId = 0;
            std::vector<uint8_t> apReq;
            if (!fenrirds::crypto::gss::parseInitialToken(inTok, tokId, apReq))
            session.gssSpnego = spnego;
            if (spnego) {
                std::vector<uint8_t> mechToken;
                if (!gss::parseSpnegoInit(inTok, mechToken, session.gssMechTypes))
                    return bindResp(LDAP_INVALID_CREDENTIALS, nullptr);
                uint16_t id = 0;
                if (!gss::parseInitialToken(mechToken, id, apReq))
                    return bindResp(LDAP_INVALID_CREDENTIALS, nullptr);
            } else {
                uint16_t id = 0;
                if (!gss::parseInitialToken(inTok, id, apReq))
                    return bindResp(LDAP_INVALID_CREDENTIALS, nullptr);
            }
            GssAcceptResult res;
            if (!g_KdcPlugin->gssAccept(apReq, res))
                return bindResp(LDAP_INVALID_CREDENTIALS, nullptr);
@@ -645,26 +711,42 @@ std::vector<uint8_t> ActiveDirectory::handleBindRequest(const uint8_t *content,
            session.gssEtype = res.sessionEtype;
            session.gssClient = res.clientPrincipal;
            session.gssState = LdapSession::GssSentApRep;
            std::vector<uint8_t> creds;

            std::vector<uint8_t> krb5reply;
            if (!res.apRep.empty()) {
                creds = {0x02, 0x00};   /* GSS AP-REP token id, no 0x60 wrapper */
                creds.insert(creds.end(), res.apRep.begin(), res.apRep.end());
                krb5reply = {0x02, 0x00};   /* GSS AP-REP token id */
                krb5reply.insert(krb5reply.end(), res.apRep.begin(), res.apRep.end());
            }
            std::vector<uint8_t> creds;
            if (spnego) {
                std::vector<uint8_t> krb5oid(gss::KRB5_OID, gss::KRB5_OID + 11);
                std::vector<uint8_t> mic = gss::getMic(session.gssKey, session.gssEtype,
                                                       true, 0, session.gssMechTypes);
                creds = gss::buildSpnegoResp(gss::ACCEPT_COMPLETED, &krb5oid, krb5reply, mic);
            } else {
                creds = krb5reply;
            }
            return bindResp(SASL_BIND_IN_PROGRESS, &creds);
        }

        if (session.gssState == LdapSession::GssSentApRep) {
            /* offer the SASL security layer: none (0x01), max message size 0 */
            std::vector<uint8_t> sec = {0x01, 0x00, 0x00, 0x00};
            auto tok = fenrirds::crypto::gss::wrap(session.gssKey, session.gssEtype, true, 0, sec);
            /* offer the SASL security layer: none|integrity|confidentiality,
             * with a max receive buffer. Wrapped (sign) with the context key. */
            uint32_t maxBuf = 0x000FFFFF;
            std::vector<uint8_t> sec = {0x03, uint8_t(maxBuf >> 16), uint8_t(maxBuf >> 8), uint8_t(maxBuf)};
            auto tok = gss::wrap(session.gssKey, session.gssEtype, true, session.sendSeq++, sec);
            session.gssState = LdapSession::GssSentSecLayer;
            return bindResp(SASL_BIND_IN_PROGRESS, &tok);
        }

        /* GssSentSecLayer: client's wrapped layer choice + authzid */
        /* GssSentSecLayer: client's wrapped layer choice (+authzid) */
        fenrirds::crypto::Bytes plain;
        if (!fenrirds::crypto::gss::unwrap(session.gssKey, session.gssEtype, false, inTok, plain))
        if (!gss::unwrap(session.gssKey, session.gssEtype, false, inTok, plain) || plain.size() < 4)
            return bindResp(LDAP_INVALID_CREDENTIALS, nullptr);
        uint8_t layer = plain[0];
        session.ssf = (layer & 0x04) ? 2 : (layer & 0x02) ? 1 : 0;
        session.recvSeq = 1;   /* the sec-layer tokens consumed seq 0 each way */
        session.sendSeq = 1;
        session.authenticated = true;
        session.bindDN = session.gssClient;
        session.gssState = LdapSession::GssNone;
+9 −1
Changes for plugin/ad/ad.h: 9 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -209,11 +209,19 @@ namespace fenrirds {
        uuid::uuid   userUid;        /* UUID if bound as a user          */
        std::string  realm;          /* domain realm for the session     */

        /* SASL GSSAPI state */
        /* SASL GSSAPI / GSS-SPNEGO state */
        enum GssState { GssNone, GssSentApRep, GssSentSecLayer } gssState = GssNone;
        std::vector<uint8_t> gssKey;        /* the context session key */
        int                  gssEtype = 0;
        std::string          gssClient;     /* client principal name@REALM */
        bool                 gssSpnego = false;
        std::vector<uint8_t> gssMechTypes; /* DER of the client's MechTypeList (for the MIC) */

        /* established SASL security layer (after a successful GSSAPI bind):
         * ssf 0 = none, 1 = integrity (sign), 2 = confidentiality (seal) */
        int                  ssf = 0;
        uint64_t             sendSeq = 0;   /* acceptor -> client */
        uint64_t             recvSeq = 0;   /* client -> acceptor */
    };

    /***************************************************************************
+13 −1
Changes for plugin/krb5/krb5.cpp: 13 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -1670,6 +1670,10 @@ bool Asn1Der::parseAuthenticator(const uint8_t *data, size_t len,
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseGeneralizedTime(icontent, icontent_len, auth.ctime);
                break;
            case 6: /* subkey (EncryptionKey) — the GSS context key */
                if (parseEncryptionKey(content, content_len, auth.subkey))
                    auth.has_subkey = true;
                break;
            case 7: /* seq-number */
                if (parseTLV(content, content_len, ioff, itag, icontent, icontent_len))
                    parseUInteger(icontent, icontent_len, auth.seq_number);
@@ -3631,10 +3635,18 @@ bool Krb5KDC::gssAccept(const std::vector<uint8_t> &apReqData, GssAcceptResult &
        return false;

    out.clientPrincipal = et.cname.name + "@" + et.cname.realm;
    /* GSS uses the authenticator subkey as the context key when the client
     * sent one (MIT/Samba/Windows do); otherwise the ticket session key. */
    if (auth.has_subkey && !auth.subkey.keydata.empty()) {
        out.sessionKey = auth.subkey.keydata;
        out.sessionEtype = auth.subkey.enctype;
    } else {
        out.sessionKey = et.session_key.keydata;
        out.sessionEtype = et.session_key.enctype;
    }

    /* KRB5_AP_OPTS_MUTUAL_REQUIRED = 0x20000000 */
    /* KRB5_AP_OPTS_MUTUAL_REQUIRED = 0x20000000: the AP-REP enc-part is
     * encrypted with the TICKET session key (not the subkey). */
    if (ap.ap_options & 0x20000000) {
        Krb5Service svc("", "", ap.ticket.sname.realm, svcKey);
        out.apRep = svc.buildApRep(auth, et.session_key);
+2 −0
Changes for plugin/krb5/krb5.h: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -470,6 +470,8 @@ namespace fenrirds {
        time_t           ctime;               /* client time           */
        uint32_t         cusec;               /* microseconds          */
        uint32_t         seq_number;          /* sequence number       */
        Krb5SessionKey   subkey;              /* [6] subkey (GSS context key) */
        bool             has_subkey = false;

        Krb5Authenticator();

Loading