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

test

parent 1424c1b2
Loading
Loading
Loading
Loading
Loading
+188 −0
Changes for tests/proto/test_netlogon.cpp: 188 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
#include "crypto/crypto.h"
#include "rpc/ndr.h"
#include "rpc/services.h"
#include "rpc/netsec.h"

using namespace fenrirds;
using namespace fenrirds::rpc;
@@ -112,7 +113,194 @@ TEST(netlogon_secure_channel) {
    NdrPull ap2(ar2.stub); uint8_t x[8]; ap2.take(x,8); ap2.u32(); ap2.u32();
    CHECK(ap2.u32() != 0);   /* status != success */

    /* --- the authenticator chain on the established channel --- */
    {   /* re-establish with the right password (the failed attempt above reset it) */
        cr::Bytes cc3 = cr::randomBytes(8);
        NdrPush r3; r3.u32(0);
        { auto u = cr::utf16le("WS1"); r3.align(4); r3.u32(4); r3.u32(0); r3.u32(4); r3.bytes(u.data(),u.size()); r3.u8(0); r3.u8(0); }
        r3.bytes(cc3.data(), 8);
        NdrPull p3(netlogonDispatch(4, r3.buf).stub); uint8_t s3[8]; p3.take(s3, 8);
        cc = cc3; sc.assign(s3, s3 + 8);
        sk = clientSessionKey(nt, cc, sc);
        ccred = clientCred(sk, cc);
        NdrPush a3; a3.u32(0);
        { auto u = cr::utf16le("WS1$"); a3.align(4); a3.u32(5); a3.u32(0); a3.u32(5); a3.bytes(u.data(),u.size()); a3.u8(0); a3.u8(0); }
        a3.u16(2);
        { auto u = cr::utf16le("WS1"); a3.align(4); a3.u32(4); a3.u32(0); a3.u32(4); a3.bytes(u.data(),u.size()); a3.u8(0); a3.u8(0); }
        a3.bytes(ccred.data(), 8); a3.align(4); a3.u32(0x612fffff);
        NdrPull ap3(netlogonDispatch(26, a3.buf).stub); uint8_t x3[8]; ap3.take(x3,8); ap3.u32(); ap3.u32();
        CHECK_EQ(ap3.u32(), uint32_t(0));
    }
    cr::Bytes seed = ccred;
    auto nextAuth = [&](NdrPush &p, uint32_t ts, cr::Bytes &wantRet) {
        cr::Bytes t = seed;
        uint32_t v = t[0] | t[1] << 8 | t[2] << 16 | uint32_t(t[3]) << 24; v += ts;
        for (int i = 0; i < 4; ++i) t[i] = uint8_t(v >> (8*i));
        p.bytes(clientCred(sk, t)); p.u32(ts);
        v += 1; for (int i = 0; i < 4; ++i) t[i] = uint8_t(v >> (8*i));
        seed = t; wantRet = clientCred(sk, t);
    };
    auto serverAndComputer = [](NdrPush &p) {
        p.u32(0x20000); { auto u = cr::utf16le("\\\\dc1"); p.u32(6); p.u32(0); p.u32(6); p.bytes(u); p.u8(0); p.u8(0); }
        p.u32(0x20004); { auto u = cr::utf16le("WS1"); p.align(4); p.u32(4); p.u32(0); p.u32(4); p.bytes(u); p.u8(0); p.u8(0); }
    };
    for (uint32_t level : {1u, 2u}) {   /* GetCapabilities: negotiated / requested flags */
        NdrPush g; serverAndComputer(g);
        cr::Bytes want; g.align(4); nextAuth(g, 1000 + level, want);
        g.bytes(std::vector<uint8_t>(12, 0));
        g.u32(level);
        auto gr = netlogonDispatch(21, g.buf, {}, "WS1");
        CHECK(!gr.fault);
        NdrPull gp(gr.stub);
        auto ret = gp.takeVec(8); gp.u32();
        CHECK(cr::equal(ret, want));
        CHECK_EQ(gp.u32(), level);
        CHECK_EQ(gp.u32(), uint32_t(0x612fffff));
        CHECK_EQ(gp.u32(), uint32_t(0));
    }
    {   /* a replayed authenticator is rejected */
        cr::Bytes keep = seed;
        cr::Bytes t = seed; cr::Bytes w;
        NdrPush g; serverAndComputer(g); g.align(4);
        uint32_t v = t[0] | t[1] << 8 | t[2] << 16 | uint32_t(t[3]) << 24; v -= 1;   /* old seed */
        for (int i = 0; i < 4; ++i) t[i] = uint8_t(v >> (8*i));
        g.bytes(clientCred(sk, t)); g.u32(0);
        g.bytes(std::vector<uint8_t>(12, 0)); g.u32(1);
        auto gr = netlogonDispatch(21, g.buf, {}, "WS1");
        NdrPull gp(gr.stub); gp.takeVec(8); gp.u32(); gp.u32(); gp.u32();
        CHECK(gp.u32() != 0);
        seed = keep;
    }
    {   /* GetDomainInfo level 1 with a minimal workstation info */
        NdrPush g; serverAndComputer(g);
        cr::Bytes want; g.align(4); nextAuth(g, 2000, want);
        g.bytes(std::vector<uint8_t>(12, 0));
        g.u32(1);
        g.u32(1); g.u32(0x20008);                   /* union + NETLOGON_WORKSTATION_INFO* */
        g.u32(0); g.u32(0);                         /* LsaPolicy */
        g.u32(0x2000c); for (int i = 0; i < 5; ++i) g.u32(0);    /* DnsHostName only */
        g.u16(0); g.u16(0); g.u32(0);               /* OsVersion */
        g.u16(20); g.u16(20); g.u32(0x20010);       /* OsName */
        for (int i = 0; i < 4; ++i) { g.u16(0); g.u16(0); g.u32(0); }
        g.u32(0x2); g.u32(0x18); g.u32(0); g.u32(0);
        { auto u = cr::utf16le("ws1.example.com"); g.u32(16); g.u32(0); g.u32(16); g.bytes(u); g.u8(0); g.u8(0); }
        { auto u = cr::utf16le("Windows 11"); g.align(4); g.u32(10); g.u32(0); g.u32(10); g.bytes(u); }
        auto gr = netlogonDispatch(29, g.buf, {}, "WS1");
        CHECK(!gr.fault);
        CHECK_EQ(gr.status, uint32_t(0));
        NdrPull gp(gr.stub);
        CHECK(cr::equal(gp.takeVec(8), want)); gp.u32();
        CHECK_EQ(gp.u32(), uint32_t(1));
        CHECK(gp.u32() != 0);
        uint16_t l, m; gp.unistrHeader(l, m); CHECK_EQ(l, uint16_t(14));   /* EXAMPLE */
        auto e = g_dir->bySamAccountName("WS1$");
        CHECK(e && e->str("operatingSystem") == "Windows 11");
        CHECK(e && e->str("dNSHostName") == "ws1.example.com");
        CHECK(e && dsdb::intValue(e->attrs, "msDS-SupportedEncryptionTypes", 0) == 0x18);
    }
    {   /* ServerPasswordSet2, then the new password authenticates */
        std::string newPw = "N3w.Machine-Pw";
        cr::Bytes u = cr::utf16le(newPw);
        cr::Bytes plain = cr::randomBytes(512 - u.size());
        plain.insert(plain.end(), u.begin(), u.end());
        uint32_t len = uint32_t(u.size());
        for (int i = 0; i < 4; ++i) plain.push_back(uint8_t(len >> (8*i)));
        cr::Bytes enc = cr::aesCfb8Encrypt(sk, cr::Bytes(16, 0), plain);
        NdrPush g; g.u32(0x20000); { auto s1 = cr::utf16le("\\\\dc1"); g.u32(6); g.u32(0); g.u32(6); g.bytes(s1); g.u8(0); g.u8(0); }
        { auto s1 = cr::utf16le("WS1$"); g.align(4); g.u32(5); g.u32(0); g.u32(5); g.bytes(s1); g.u8(0); g.u8(0); }
        g.u16(2);
        { auto s1 = cr::utf16le("WS1"); g.align(4); g.u32(4); g.u32(0); g.u32(4); g.bytes(s1); g.u8(0); g.u8(0); }
        cr::Bytes want; g.align(4); nextAuth(g, 3000, want);
        g.bytes(enc);
        auto gr = netlogonDispatch(30, g.buf, {}, "WS1");
        CHECK(!gr.fault);
        NdrPull gp(gr.stub);
        CHECK(cr::equal(gp.takeVec(8), want)); gp.u32();
        CHECK_EQ(gp.u32(), uint32_t(0));
        auto e = g_dir->bySamAccountName("WS1$");
        CHECK(e && cr::equal(cr::bytes(g_dir->secretsOf(e->objectGuid).ntHash), cr::ntHash(newPw)));
    }
    {   /* DsrGetDcNameEx2 for our domain, and for a foreign one */
        setDcIdentity("dc1.example.com", "10.1.1.50");
        auto call = [](const std::string &dom) {
            NdrPush g;
            g.u32(0);                      /* ComputerName */
            g.u32(0);                      /* AccountName */
            g.u32(0);                      /* AllowableAccountControlBits */
            g.u32(0x20000); { auto u = cr::utf16le(dom); g.u32(uint32_t(dom.size()+1)); g.u32(0); g.u32(uint32_t(dom.size()+1)); g.bytes(u); g.u8(0); g.u8(0); }
            g.u32(0);                      /* DomainGuid */
            g.u32(0);                      /* SiteName */
            g.u32(0x40000000);             /* DS_RETURN_DNS_NAME */
            return netlogonDispatch(34, g.buf);
        };
        auto ok = call("example.com");
        CHECK(!ok.fault);
        CHECK_EQ(ok.status, uint32_t(0));
        NdrPull gp(ok.stub);
        CHECK(gp.u32() != 0);
        for (int i = 0; i < 2; ++i) gp.u32();
        CHECK_EQ(gp.u32(), uint32_t(1));   /* DS_INET_ADDRESS */
        gp.takeVec(16); gp.u32(); gp.u32();
        CHECK_EQ(gp.u32(), uint32_t(0xe00003fd));
        gp.u32(); gp.u32();
        CHECK(gp.unistrBuffer() == "\\\\dc1.example.com");
        CHECK(gp.unistrBuffer() == "\\\\10.1.1.50");
        auto bad = call("other.org");
        CHECK_EQ(bad.status, uint32_t(1355));
    }
    {   /* the secure channel key is there for the schannel bind */
        std::vector<uint8_t> k;
        CHECK(netlogonChannelKey("ws1", k));
        CHECK(cr::equal(k, sk));
        CHECK(!netlogonChannelKey("nosuch", k));
    }

    unlink(path.c_str()); unlink((path + ".lock").c_str()); unlink((path + ".dom").c_str());
}

TEST(netsec_nl_auth_message) {
    /* what W11 sends: flags 0x17, OEM domain + computer, DNS domain, UTF-8 computer */
    std::vector<uint8_t> t = {0,0,0,0, 0x17,0,0,0};
    for (char c : std::string("EXAMPLE")) t.push_back(uint8_t(c)); t.push_back(0);
    for (char c : std::string("W11OLD")) t.push_back(uint8_t(c)); t.push_back(0);
    const uint8_t dns[] = {7,'e','x','a','m','p','l','e',3,'c','o','m',0, 6,'W','1','1','O','L','D',0};
    t.insert(t.end(), dns, dns + sizeof dns);
    NlAuthNegotiate n;
    CHECK(parseNlAuthNegotiate(t, n));
    CHECK(n.domain == "EXAMPLE");
    CHECK(n.computer == "W11OLD");
    CHECK(n.dnsDomain == "example.com");
    auto r = buildNlAuthResponse();
    CHECK_EQ(r.size(), size_t(12));
    CHECK_EQ(r[0], uint8_t(1));
}

TEST(netsec_seal_roundtrip) {
    cr::Bytes sk = cr::randomBytes(16);
    NetsecContext client(sk, "WS1"), server(sk, "WS1");
    for (bool seal : {true, false}) {
        for (int i = 0; i < 3; ++i) {
            std::vector<uint8_t> msg = cr::randomBytes(48), wire = msg;
            auto sig = client.wrapRequest(wire, seal, 56);
            CHECK_EQ(sig.size(), size_t(56));
            CHECK(seal ? wire != msg : wire == msg);
            CHECK(server.unwrapRequest(wire, sig, seal));
            CHECK(wire == msg);
            std::vector<uint8_t> resp = cr::randomBytes(32), rw = resp;
            auto rs = server.wrapResponse(rw, seal, 32);
            CHECK(client.unwrapResponse(rw, rs, seal));
            CHECK(rw == resp);
        }
    }
    /* a flipped byte or a replay fails */
    std::vector<uint8_t> msg = cr::randomBytes(32), wire = msg;
    auto sig = client.wrapRequest(wire, true);
    std::vector<uint8_t> bad = wire; bad[5] ^= 1;
    NetsecContext s2 = server;
    CHECK(!s2.unwrapRequest(bad, sig, true));
    CHECK(server.unwrapRequest(wire, sig, true));
    std::vector<uint8_t> again = msg; auto sig2 = sig;
    CHECK(!server.unwrapRequest(again, sig2, true));
}

CHECK_MAIN