Commit a6c56836 authored by jan.koester's avatar jan.koester
Browse files

test

parent 33ce07f1
Loading
Loading
Loading
Loading
Loading
+171 −8
Changes for plugin/smb/smb.cpp: 171 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -6,6 +6,10 @@
#include <cstring>
#include <iostream>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

#include "crypto/crypto.h"
#include "crypto/gss.h"
#include "crypto/ntlm.h"
@@ -21,6 +25,7 @@
namespace fenrirds {

namespace {
    constexpr uint8_t AUTH_TYPE_SPNEGO = 9, AUTH_TYPE_KRB5 = 16;
    uint16_t rd16(const uint8_t *p) { return uint16_t(p[0] | p[1] << 8); }
    uint32_t rd32(const uint8_t *p) { return uint32_t(p[0]) | p[1]<<8 | p[2]<<16 | uint32_t(p[3])<<24; }
    uint64_t rd64(const uint8_t *p) { uint64_t v=0; for(int i=0;i<8;i++) v|=uint64_t(p[i])<<(8*i); return v; }
@@ -164,19 +169,38 @@ void RpcPipe::handlePdu(const std::vector<uint8_t> &pdu) {
            std::cerr << "[RPC] BIND auth=" << b.hasAuth << " type=" << int(b.authType)
                      << " level=" << int(b.authLevel) << " token=" << b.authToken.size() << "B"
                      << " iface=" << _name << std::endl;
            auto known = [](const rpc::SyntaxId &a) {
                return rpc::isLsarpc(a.uuid) || rpc::isSamr(a.uuid) || rpc::isNetlogon(a.uuid);
            auto known = [this](const rpc::SyntaxId &a) {
                return rpc::isLsarpc(a.uuid) || rpc::isSamr(a.uuid) || rpc::isNetlogon(a.uuid) ||
                       (_tcp && (rpc::isDrsuapi(a.uuid) || rpc::isEpm(a.uuid)));
            };
            std::string secAddr = _tcp ? std::to_string(_localPort) : "\\PIPE\\" + _name;
            _boundAbstract = b.contexts[0].abstractSyntax;
            _bound = true;
            if (b.hasAuth && b.authType == rpc::AUTH_TYPE_SCHANNEL) {
            if (b.hasAuth && (b.authType == AUTH_TYPE_SPNEGO || b.authType == AUTH_TYPE_KRB5)) {
                if (h.pduType == rpc::PDU_ALTER && _gss.active) {
                    /* SPNEGO's third leg: the client's AP-REP */
                    std::vector<uint8_t> out = gssThirdLeg(b.authToken);
                    resp = rpc::buildBindAck(b, secAddr, out, b.authType, b.authLevel, known, b.authContextId);
                } else {
                    bool ok = false;
                    std::vector<uint8_t> out;
                    if (known(b.contexts[0].abstractSyntax))
                        out = gssBind(b, ok);
                    if (ok)
                        resp = rpc::buildBindAck(b, secAddr, out, b.authType, b.authLevel, known, b.authContextId);
                    else if (!known(b.contexts[0].abstractSyntax))
                        resp = rpc::buildBindAck(b, secAddr, {}, 0, 0, known);   /* rejects the context */
                    else
                        resp = rpc::buildBindNak(h.callId);
                }
            } else if (b.hasAuth && b.authType == rpc::AUTH_TYPE_SCHANNEL) {
                /* netlogon secure channel: the client names its computer, we
                 * take the session key of its ServerAuthenticate3 */
                rpc::NlAuthNegotiate neg;
                std::vector<uint8_t> sk;
                if (rpc::parseNlAuthNegotiate(b.authToken, neg) && rpc::netlogonChannelKey(neg.computer, sk)) {
                    _netsec = rpc::NetsecContext(sk, neg.computer);
                    resp = rpc::buildBindAck(b, "\\PIPE\\" + _name, rpc::buildNlAuthResponse(),
                    resp = rpc::buildBindAck(b, secAddr, rpc::buildNlAuthResponse(),
                                             b.authType, b.authLevel, known, b.authContextId);
                    std::cerr << "[RPC] schannel bind for '" << neg.computer << "'" << std::endl;
                } else {
@@ -184,7 +208,7 @@ void RpcPipe::handlePdu(const std::vector<uint8_t> &pdu) {
                    resp = rpc::buildBindNak(h.callId);
                }
            } else {
                resp = rpc::buildBindAck(b, "\\PIPE\\" + _name, {}, 0, 0, known);
                resp = rpc::buildBindAck(b, secAddr, {}, 0, 0, known);
            }
            if (h.pduType == rpc::PDU_ALTER && resp.size() > 2 && resp[2] == rpc::PDU_BIND_ACK)
                resp[2] = rpc::PDU_ALTER_ACK;
@@ -193,6 +217,8 @@ void RpcPipe::handlePdu(const std::vector<uint8_t> &pdu) {
        }
    } else if (h.pduType == rpc::PDU_AUTH3) {
        std::cerr << "[RPC] AUTH3 received (authLen=" << h.authLength << ")" << std::endl;
        if (_gss.active && h.authLength && h.authLength <= pdu.size())
            gssThirdLeg(std::vector<uint8_t>(pdu.end() - h.authLength, pdu.end()));
    } else if (h.pduType == rpc::PDU_REQUEST) {
        rpc::RequestPdu req;
        if (rpc::parseRequest(pdu.data(), pdu.size(), req)) {
@@ -209,6 +235,23 @@ void RpcPipe::handlePdu(const std::vector<uint8_t> &pdu) {
                        req.stub.resize(req.stub.size() - req.authPadLength);
                    resp = dispatch(req, true);
                }
            } else if (req.hasAuth && (req.authType == AUTH_TYPE_SPNEGO || req.authType == AUTH_TYPE_KRB5)) {
                bool ok = _gss.active;
                if (ok && req.authLevel == rpc::AUTH_LEVEL_PRIVACY) {
                    std::vector<uint8_t> tok = req.authToken, plain;
                    tok.insert(tok.end(), req.stub.begin(), req.stub.end());
                    ok = crypto::gss::unwrap(_gss.key, _gss.etype, false, tok, plain, nullptr, true);
                    if (ok) req.stub = plain;
                }
                if (!ok) {
                    std::cerr << "[RPC] GSS request rejected (unwrap) stub=" << req.stub.size()
                              << " pad=" << int(req.authPadLength) << " tok=" << crypto::hex(req.authToken) << std::endl;
                    resp = rpc::buildFault(req.callId, req.contextId, 0x00000005);
                } else {
                    if (req.authPadLength <= req.stub.size())
                        req.stub.resize(req.stub.size() - req.authPadLength);
                    resp = dispatch(req);
                }
            } else {
                resp = dispatch(req);
            }
@@ -217,6 +260,73 @@ void RpcPipe::handlePdu(const std::vector<uint8_t> &pdu) {
    _out.insert(_out.end(), resp.begin(), resp.end());
}

/* bind leg: accept the AP-REQ. DCE style (GSS flag 0x1000, what RPC
 * clients ask for) answers with the raw AP-REP and waits for the
 * client's AP-REP (AUTH3, or an ALTER_CONTEXT under SPNEGO). */
std::vector<uint8_t> RpcPipe::gssBind(const rpc::BindPdu &b, bool &ok) {
    ok = false;
    bool spnego = b.authType == AUTH_TYPE_SPNEGO;
    std::vector<uint8_t> apReq, mechTypes, inner;
    if (spnego) {
        if (!crypto::gss::parseSpnegoInit(b.authToken, apReq, mechTypes))
            return {};
    } else {
        apReq = b.authToken;
    }
    uint16_t id = 0;
    if (!crypto::gss::parseInitialToken(apReq, id, inner))
        inner = apReq;
    GssAcceptResult res;
    if (!g_KdcPlugin || !g_KdcPlugin->gssAccept(inner, res)) {
        std::cerr << "[RPC] GSS bind: AP-REQ not accepted" << std::endl;
        return {};
    }
    bool dce = (res.gssFlags & 0x1000) != 0;
    _gss = Gss();
    _gss.active = true;
    _gss.spnego = spnego;
    _gss.type = b.authType;
    _gss.level = b.authLevel;
    _gss.ctxId = b.authContextId;
    _gss.key = res.sessionKey;
    _gss.etype = res.sessionEtype;
    _gss.accSubkey = res.acceptorSubkey;
    _gss.sendSeq = res.acceptorSeq;
    _gss.principal = res.clientPrincipal;
    std::cerr << "[RPC] GSS bind " << res.clientPrincipal << " type=" << int(b.authType)
              << " level=" << int(b.authLevel) << " flags=0x" << std::hex << res.gssFlags << std::dec
              << " etype=" << res.sessionEtype << " dce=" << dce << std::endl;
    std::vector<uint8_t> krb = dce ? res.apRep : crypto::gss::wrapInitialToken(res.apRep, 0x0200);
    ok = true;
    if (!spnego)
        return krb;
    _gss.mechOid = crypto::gss::firstMechOid(mechTypes);
    _gss.mechTypes = mechTypes;
    return crypto::gss::buildSpnegoResp(dce ? crypto::gss::ACCEPT_INCOMPLETE : crypto::gss::ACCEPT_COMPLETED,
                                        &_gss.mechOid, krb, {});
}

/* the client's AP-REP of a DCE-style context; under SPNEGO the reply
 * completes the negotiation */
std::vector<uint8_t> RpcPipe::gssThirdLeg(const std::vector<uint8_t> &tok) {
    std::cerr << "[RPC] GSS third leg (" << tok.size() << "B) for " << _gss.principal << std::endl;
    if (!_gss.spnego)
        return {};
    /* the client's NegTokenResp carries its AP-REP and, as RPC clients
     * send it, a mechListMIC: then ours over the same mechTypes is due
     * (RFC 4178 5), the first token of our sequence */
    std::vector<uint8_t> rt, mic;
    int st = 0;
    std::vector<uint8_t> ourMic;
    if (crypto::gss::parseSpnegoResp(tok, rt, mic, st) && !mic.empty()) {
        if (!crypto::gss::verifyMic(_gss.key, _gss.etype, false, mic, _gss.mechTypes))
            std::cerr << "[RPC] GSS third leg: client mechListMIC does not verify" << std::endl;
        ourMic = crypto::gss::getMic(_gss.key, _gss.etype, true, _gss.sendSeq++, _gss.mechTypes,
                                     _gss.accSubkey);
    }
    return crypto::gss::buildSpnegoResp(crypto::gss::ACCEPT_COMPLETED, nullptr, {}, ourMic);
}

std::vector<uint8_t> RpcPipe::dispatch(const rpc::RequestPdu &req, bool schannel) {
    rpc::DispatchResult res;
    const char *iface = "?";
@@ -230,6 +340,17 @@ std::vector<uint8_t> RpcPipe::dispatch(const rpc::RequestPdu &req, bool schannel
        iface = "netlogon";
        res = rpc::netlogonDispatch(req.opnum, req.stub, _sessionKey,
                                    schannel ? _netsec.computer() : std::string());
    } else if (_tcp && rpc::isEpm(_boundAbstract.uuid)) {
        iface = "epm";
        res = rpc::epmDispatch(req.opnum, req.stub, _localIp, _rpcPort);
    } else if (_tcp && rpc::isDrsuapi(_boundAbstract.uuid)) {
        iface = "drsuapi";
        if (_gss.active) {
            res = rpc::drsuapiDispatch(req.opnum, req.stub);
        } else {
            res.fault = true;
            res.faultCode = 0x00000005;   /* access denied: needs an authenticated bind */
        }
    } else {
        res.fault = true;
        res.faultCode = 0x1C010003;   /* nca_unk_if */
@@ -246,6 +367,15 @@ std::vector<uint8_t> RpcPipe::dispatch(const rpc::RequestPdu &req, bool schannel
    }
    if (res.fault)
        return rpc::buildFault(req.callId, req.contextId, res.faultCode);
    if (req.hasAuth && (req.authType == AUTH_TYPE_SPNEGO || req.authType == AUTH_TYPE_KRB5) && _gss.active) {
        bool seal = req.authLevel == rpc::AUTH_LEVEL_PRIVACY;
        return rpc::buildAuthResponse(req.callId, req.contextId, res.stub, req.authType, req.authLevel,
                                      req.authContextId, [&](std::vector<uint8_t> &data) {
            if (seal)
                return crypto::gss::wrapDce(_gss.key, _gss.etype, true, _gss.sendSeq++, data, _gss.accSubkey);
            return crypto::gss::getMic(_gss.key, _gss.etype, true, _gss.sendSeq++, data, _gss.accSubkey);
        });
    }
    if (schannel) {
        bool seal = req.authLevel == rpc::AUTH_LEVEL_PRIVACY;
        size_t sigLen = req.authToken.size();
@@ -275,13 +405,21 @@ namespace {
}

SmbServer::SmbServer(AuthBackend &adminBackend, const std::string &bindAddr, int port,
                     const std::string &dcHost)
    : _AdminBackend(adminBackend), _BindAddr(bindAddr), _Port(port), _DcHost(dcHost) {
                     const std::string &dcHost, int epmPort, int rpcPort)
    : _AdminBackend(adminBackend), _BindAddr(bindAddr), _Port(port),
      _EpmPort(epmPort), _RpcPort(rpcPort), _DcHost(dcHost) {
    DomainRegistry::instance().setAdminBackend(&adminBackend);
    auto g = crypto::randomBytes(16);
    std::memcpy(_ServerGuid, g.data(), 16);
    _ServerSockets.push_back(new netplus::tcp(bindAddr.c_str(), port, 128, 0));
    std::cerr << "SMB plugin listening on " << bindAddr << ":" << port << std::endl;
    /* DCE/RPC over TCP in the same event loop: the endpoint mapper and the
     * port it hands out (connections are told apart by their local port) */
    for (int p : {epmPort, rpcPort}) {
        if (p <= 0) continue;
        _ServerSockets.push_back(new netplus::tcp(bindAddr.c_str(), p, 128, 0));
        std::cerr << "RPC (ncacn_ip_tcp) listening on " << bindAddr << ":" << p << std::endl;
    }
}

SmbServer::~SmbServer() {
@@ -303,7 +441,20 @@ void SmbServer::CreateConnection(std::shared_ptr<netplus::con> &res) {
    res = std::make_shared<netplus::con>(this);
}
void SmbServer::ConnectEvent(netplus::con &c, const int, ULONG_PTR) {
    std::lock_guard<std::mutex> l(_Lock); _Conns[&c] = Conn();
    Conn conn;
    /* which listener: our side of the connection */
    sockaddr_in la{};
    socklen_t ll = sizeof(la);
    if (!c.slots.empty() && c.slots[0].csock &&
        getsockname(c.slots[0].csock->fd(), reinterpret_cast<sockaddr*>(&la), &ll) == 0 &&
        la.sin_family == AF_INET) {
        int lport = ntohs(la.sin_port);
        if (lport == _EpmPort || lport == _RpcPort) {
            conn.tcpRpc = new RpcPipe("tcp");
            conn.tcpRpc->setTcp(ntohl(la.sin_addr.s_addr), uint16_t(lport), uint16_t(_RpcPort));
        }
    }
    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 */
@@ -314,6 +465,7 @@ void SmbServer::DisconnectEvent(netplus::con &c, const int, ULONG_PTR) {
            flush(o.second);
            delete o.second.pipe;
        }
        delete it->second.tcpRpc;
        _Conns.erase(it);
    }
}
@@ -325,6 +477,17 @@ void SmbServer::RequestEvent(netplus::con &curcon, const int, ULONG_PTR) {
    if (it == _Conns.end()) return;
    Conn &c = it->second;

    if (c.tcpRpc) {   /* DCE/RPC over TCP: the PDU stream itself */
        const uint8_t *d = reinterpret_cast<const uint8_t*>(curcon.RecvData.data());
        c.tcpRpc->write(d, curcon.RecvData.size());
        curcon.RecvData.clear();
        while (c.tcpRpc->hasOutput()) {
            auto out = c.tcpRpc->read(65536);
            curcon.SendData.insert(curcon.SendData.end(), out.begin(), out.end());
        }
        return;
    }

    std::vector<uint8_t> rb(reinterpret_cast<const uint8_t*>(curcon.RecvData.data()),
                            reinterpret_cast<const uint8_t*>(curcon.RecvData.data()) + curcon.RecvData.size());
    size_t consumed = 0;
+33 −1
Changes for plugin/smb/smb.h: 33 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -43,10 +43,32 @@ namespace fenrirds {
        /* dequeue queued response bytes (up to max) */
        std::vector<uint8_t> read(size_t max);
        bool hasOutput() const { return _outPos < _out.size(); }
        /* an ncacn_ip_tcp endpoint: also serves the endpoint mapper and
         * drsuapi; towers point at localIp:rpcPort (host byte order) */
        void setTcp(uint32_t localIp, uint16_t localPort, uint16_t rpcPort) {
            _tcp = true; _localIp = localIp; _localPort = localPort; _rpcPort = rpcPort;
        }

    private:
        void handlePdu(const std::vector<uint8_t> &pdu);
        std::vector<uint8_t> dispatch(const rpc::RequestPdu &req, bool schannel = false);
        /* Kerberos (16) / SPNEGO (9) RPC authentication, DCE style */
        std::vector<uint8_t> gssBind(const rpc::BindPdu &b, bool &ok);
        std::vector<uint8_t> gssThirdLeg(const std::vector<uint8_t> &tok);

        struct Gss {
            bool active = false;        /* bind accepted the AP-REQ */
            bool spnego = false;
            uint8_t type = 0, level = 0;
            uint32_t ctxId = 0;
            std::vector<uint8_t> key;
            int etype = 0;
            bool accSubkey = false;
            uint64_t sendSeq = 0;
            std::string principal;
            std::vector<uint8_t> mechOid;
            std::vector<uint8_t> mechTypes;   /* DER, for the mechListMIC */
        };

        std::string          _name;
        std::vector<uint8_t> _in;        /* request assembly */
@@ -57,6 +79,11 @@ namespace fenrirds {
        std::vector<uint8_t> _sessionKey;
        std::vector<uint8_t> _appKey;
        rpc::NetsecContext   _netsec;    /* netlogon schannel bind (auth type 68) */
        Gss                  _gss;
        bool                 _tcp = false;
        uint32_t             _localIp = 0;
        uint16_t             _localPort = 0;
        uint16_t             _rpcPort = 0;
    };

    /***************************************************************************
@@ -73,8 +100,10 @@ namespace fenrirds {
     **************************************************************************/
    class VISIBILITY SmbServer : public netplus::eventapi, public Plugin {
    public:
        /* epmPort/rpcPort: the endpoint mapper and the one DCE/RPC-over-TCP
         * port (drsuapi, lsarpc, samr, netlogon); 0 disables */
        SmbServer(AuthBackend &adminBackend, const std::string &bindAddr,
                  int port, const std::string &dcHost);
                  int port, const std::string &dcHost, int epmPort = 135, int rpcPort = 49152);
        ~SmbServer() override;

        const char *name() const override { return "smb"; }
@@ -140,6 +169,7 @@ namespace fenrirds {
            std::map<uint64_t, Session> sessions;
            std::map<uint64_t, Open> opens;      /* volatile file id -> open */
            uint64_t nextFid = 1;
            RpcPipe *tcpRpc = nullptr;           /* an ncacn_ip_tcp connection */
        };

        /* one response of a (possibly compounded) reply */
@@ -191,6 +221,8 @@ namespace fenrirds {
        AuthBackend &_AdminBackend;
        std::string  _BindAddr;
        int          _Port;
        int          _EpmPort = 135;
        int          _RpcPort = 49152;
        std::string  _DcHost;
        uint8_t      _ServerGuid[16];

+2 −0
Changes for src/CMakeLists.txt: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -44,6 +44,8 @@ list(APPEND fenrirdsSrcs
    rpc/samr.cpp
    rpc/netlogon.cpp
    rpc/netsec.cpp
    rpc/epm.cpp
    rpc/drsuapi.cpp
    smb/smb2.cpp
)

+26 −1
Changes for src/crypto/gss.cpp: 26 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -135,6 +135,29 @@ std::vector<uint8_t> wrap(const Bytes &key, int etype, bool acceptor, uint64_t s
    return out;
}

std::vector<uint8_t> wrapDce(const Bytes &key, int etype, bool acceptor, uint64_t seq,
                             Bytes &data, bool acceptorSubkey) {
    int usage = acceptor ? ACCEPTOR_SEAL : INITIATOR_SEAL;
    /* as Windows does: 16 filler octets (EC), encrypt data || filler ||
     * header (RRC = 0 in the encrypted copy) */
    const uint16_t ec = 16;
    auto inner = cfxHeader(acceptor, true, ec, 0, seq, acceptorSubkey);
    Bytes plain(data);
    plain.insert(plain.end(), ec, 0);
    plain.insert(plain.end(), inner.begin(), inner.end());
    Bytes enc = krb5::encrypt(etype, key, usage, plain);   /* conf | data | filler | hdr, HMAC */
    const size_t ck = 12;                                   /* HMAC-SHA1-96 */
    const uint16_t rrc = uint16_t(16 + ck);
    /* rotate right by RRC + EC (the DCE quirk): filler, header copy and
     * checksum come first, then the confounder, then the data */
    std::rotate(enc.begin(), enc.end() - (rrc + ec), enc.end());
    std::vector<uint8_t> out = cfxHeader(acceptor, true, ec, rrc, seq, acceptorSubkey);
    size_t head = rrc + ec + 16;                            /* + confounder */
    out.insert(out.end(), enc.begin(), enc.begin() + head);
    data.assign(enc.begin() + head, enc.end());
    return out;
}

const uint8_t SPNEGO_OID[8] = {0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02};

std::vector<uint8_t> getMic(const Bytes &key, int etype, bool acceptor, uint64_t seq, const Bytes &data,
@@ -174,7 +197,7 @@ bool verifyMic(const Bytes &key, int etype, bool senderIsAcceptor,
}

bool unwrap(const Bytes &key, int etype, bool senderIsAcceptor,
            const std::vector<uint8_t> &token, Bytes &plaintext, bool *wasSealed) {
            const std::vector<uint8_t> &token, Bytes &plaintext, bool *wasSealed, bool dce) {
    if (token.size() < 16 || token[0] != 0x05 || token[1] != 0x04 || token[3] != 0xff)
        return false;
    bool acceptor = token[2] & 0x01;
@@ -185,6 +208,8 @@ bool unwrap(const Bytes &key, int etype, bool senderIsAcceptor,
        *wasSealed = sealed;
    uint16_t ec = uint16_t(token[4] << 8 | token[5]);
    uint16_t rrc = uint16_t(token[6] << 8 | token[7]);
    if (dce && sealed)
        rrc = uint16_t(rrc + ec);
    /* undo the right rotation of everything after the header */
    Bytes data(token.begin() + 16, token.end());
    if (!data.empty() && rrc) {
+10 −1
Changes for src/crypto/gss.h: 10 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -39,11 +39,20 @@ namespace fenrirds {
            std::vector<uint8_t> wrap(const Bytes &key, int etype, bool acceptor,
                                      uint64_t seq, const Bytes &plaintext, bool seal = false,
                                      bool acceptorSubkey = false);
            /* DCE/RPC style sealing (MS-KILE 3.4.5.4, GSS_WrapEx without
             * header signing): `data` (already padded to 16) is encrypted in
             * place; the returned auth_value is the token header plus the
             * rotated trailer (RRC = 16 + checksum) and confounder. To open
             * one, pass auth_value || data to unwrap(). */
            std::vector<uint8_t> wrapDce(const Bytes &key, int etype, bool acceptor, uint64_t seq,
                                         Bytes &data, bool acceptorSubkey = false);
            /* verify/decrypt and strip a CFX Wrap token (sealed or not, any
             * RRC); false on a bad checksum. wasSealed reports which it was. */
            /* dce: a DCE/RPC token (auth_value || data), where the EC filler
             * is rotated along but not counted in RRC (Windows, Heimdal) */
            bool unwrap(const Bytes &key, int etype, bool senderIsAcceptor,
                        const std::vector<uint8_t> &token, Bytes &plaintext,
                        bool *wasSealed = nullptr);
                        bool *wasSealed = nullptr, bool dce = false);
            /* CFX MIC token (RFC 4121 §4.2.6.1): a detached signature over data */
            std::vector<uint8_t> getMic(const Bytes &key, int etype, bool acceptor,
                                        uint64_t seq, const Bytes &data, bool acceptorSubkey = false);
Loading