Commit 8c5160bb authored by jan.koester's avatar jan.koester
Browse files

test

parent 1bef768e
Loading
Loading
Loading
Loading
Loading
+84 −1
Changes for src/admin.cpp: 84 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -50,9 +50,22 @@
#include "session.h"
#include "export.h"
#include "import.h"
#include "krb5.h"

namespace authdb {

    /* Hex-encode raw bytes to a lowercase hex string */
    static std::string hexEncode(const std::vector<uint8_t> &data) {
        static const char hex[] = "0123456789abcdef";
        std::string out;
        out.reserve(data.size() * 2);
        for (uint8_t b : data) {
            out.push_back(hex[b >> 4]);
            out.push_back(hex[b & 0x0F]);
        }
        return out;
    }

    static void sendJson(libhttppp::HttpRequest &curreq, json_object *jobj) {
        const char *str = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN);
        size_t len = strlen(str);
@@ -203,6 +216,8 @@ namespace authdb {
            uid.generate();
            class UserData udat(uid);
            bool username = false;
            std::string capturedRawPw;
            std::string capturedUsername;

            for (const auto &curformdat : curform.multipartData()) {
                if (curformdat.value.empty()) continue;
@@ -242,11 +257,15 @@ namespace authdb {
                    udat.append(cudat);
                    delete[] cudat.data;
                } else if (cudat.storage == TextStorage) {
                    if (strcmp(cudat.fieldname, "username") == 0) username = true;
                    if (strcmp(cudat.fieldname, "username") == 0) {
                        username = true;
                        capturedUsername = std::string(curformdat.value.begin(), curformdat.value.end());
                    }
                    if (strcmp(cudat.fieldname, "pwhash") == 0) {
                        Hash hash;
                        std::string pwhash, inpw;
                        std::copy(curformdat.value.begin(), curformdat.value.end(), std::back_inserter(inpw));
                        capturedRawPw = inpw;
                        hash.hash(inpw, pwhash);
                        cudat.datasize = pwhash.length() + 1;
                        cudat.data = new char[cudat.datasize];
@@ -277,6 +296,33 @@ namespace authdb {

            if (!username) throw AuthBackendError("username not set!");

            /*
             * Compute Kerberos long-term key (RFC 3962 string-to-key) from
             * the raw password so that MIT / Heimdal kinit can authenticate.
             * Salt = REALM + username, where REALM = upper-case domain name.
             */
            if (!capturedRawPw.empty() && !capturedUsername.empty()
                && strcmp(cdid, "admin") != 0) {
                try {
                    uuid::uuid did;
                    did.parse(cdid);
                    class DomainData ddat(did);
                    Domain dom;
                    size_t dpos = sizeof(AuthHeader);
                    dom.info(_AdminBackend, ddat, dpos);
                    std::string domainName = ddat.getDomainName();
                    std::string realm = domainName;
                    std::transform(realm.begin(), realm.end(), realm.begin(),
                                   [](unsigned char c){ return std::toupper(c); });
                    std::string salt = realm + capturedUsername;
                    auto krbKeyBytes = Krb5Crypto::string2key(
                        capturedRawPw, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
                    udat.setKrbKey(hexEncode(krbKeyBytes));
                } catch (...) {
                    /* Non-fatal: user is created without krbkey */
                }
            }

            User user;
            user.create(*backend, &udat);

@@ -315,6 +361,7 @@ namespace authdb {

            std::shared_ptr<authdb::AuthData> editrec = std::make_shared<authdb::AuthData>(uid);
            bool data = false;
            std::string editRawPw;

            libhttppp::HttpForm curform;
            curform.parse(curreq);
@@ -356,6 +403,7 @@ namespace authdb {
                        sha512 hash;
                        std::string pwhash, inpw;
                        inpw.assign(curformdat.value.begin(), curformdat.value.end());
                        editRawPw = inpw;
                        hash.hash(inpw, pwhash);
                        curec->datasize = pwhash.length() + 1;
                        curec->data = new char[curec->datasize];
@@ -394,6 +442,41 @@ namespace authdb {
                editRecord(*backend, *editrec, UserData);
            }

            /*
             * If the password was changed, recompute the Kerberos key.
             * Look up the username from the existing user record.
             */
            if (!editRawPw.empty() && strcmp(cdid, "admin") != 0) {
                try {
                    User user;
                    class UserData udat(uid);
                    size_t upos = sizeof(AuthHeader);
                    user.info(*backend, udat, upos);

                    std::string uname = udat.getUsername();
                    if (!uname.empty()) {
                        uuid::uuid did;
                        did.parse(cdid);
                        class DomainData ddat(did);
                        Domain dom;
                        size_t dpos = sizeof(AuthHeader);
                        dom.info(_AdminBackend, ddat, dpos);
                        std::string domainName = ddat.getDomainName();
                        std::string realm = domainName;
                        std::transform(realm.begin(), realm.end(), realm.begin(),
                                       [](unsigned char c){ return std::toupper(c); });
                        std::string salt = realm + uname;
                        auto krbKeyBytes = Krb5Crypto::string2key(
                            editRawPw, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
                        udat.setKrbKey(hexEncode(krbKeyBytes));
                        upos = sizeof(AuthHeader);
                        user.edit(*backend, udat, upos);
                    }
                } catch (...) {
                    /* Non-fatal */
                }
            }

            json_object *jobj = json_object_new_object();
            json_object_object_add(jobj, "status", json_object_new_string("ok"));
            sendJson(curreq, jobj);
+41 −0
Changes for src/api.cpp: 41 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -44,6 +44,8 @@
#include "client.h"
#include "gpo.h"
#include "session.h"
#include "domain.h"
#include "krb5.h"

namespace authdb {

@@ -58,6 +60,18 @@ namespace {
        return diff == 0;
    }

    // Hex-encode raw bytes to a lowercase hex string
    std::string hexEncode(const std::vector<uint8_t> &data) {
        static const char hex[] = "0123456789abcdef";
        std::string out;
        out.reserve(data.size() * 2);
        for (uint8_t b : data) {
            out.push_back(hex[b >> 4]);
            out.push_back(hex[b & 0x0F]);
        }
        return out;
    }

    // Helper: get required top-level "commands" array and type-check it
    json_object* get_commands_array_or_throw(json_object* request) {
        json_object* cmd = json_object_object_get(request, "commands");
@@ -557,6 +571,33 @@ public:
                    }

                    udat.setPwHash(newhash.c_str());

                    /*
                     * Recompute Kerberos long-term key from raw new password.
                     * Salt = REALM + username, REALM = upper-case domain name.
                     */
                    try {
                        DomainBackend dbackend(_Backend);
                        uuid::uuid did_val = _Did;
                        class DomainData ddat(did_val);
                        Domain dom;
                        size_t dpos = sizeof(AuthHeader);
                        dom.info(_Backend, ddat, dpos);
                        std::string domainName = ddat.getDomainName();
                        std::string realm = domainName;
                        std::transform(realm.begin(), realm.end(), realm.begin(),
                                       [](unsigned char c){ return std::toupper(c); });
                        std::string uname = udat.getUsername();
                        if (!uname.empty()) {
                            std::string salt = realm + uname;
                            auto krbKeyBytes = Krb5Crypto::string2key(
                                newpw, salt, KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
                            udat.setKrbKey(hexEncode(krbKeyBytes));
                        }
                    } catch (...) {
                        /* Non-fatal: krbkey update failed */
                    }

                    ustart = sizeof(AuthHeader);
                    user.edit(_Backend, udat, ustart);

+51 −5
Changes for src/krb5.cpp: 51 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -66,6 +66,27 @@ namespace {
        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; }
@@ -2872,6 +2893,7 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le
        /* 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;
@@ -2920,6 +2942,7 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le
                }

                if (pdType == KRB5_PADATA_ENC_TIMESTAMP && !pdValue.empty()) {
                    foundPaEncTimestamp = true;
                    try {
                        auto pa_plain = Krb5Crypto::decrypt(client_key, pdValue,
                                                             KRB5_KU_AS_REQ_PA_ENC_TS);
@@ -2940,12 +2963,21 @@ std::vector<uint8_t> Krb5KDC::handleStandardAsReq(const uint8_t *data, size_t le
        }

        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");
            }

            /*
             * Return PREAUTH_REQUIRED (not PREAUTH_FAILED) with ETYPE-INFO2.
             * MIT kinit sends the first AS-REQ speculatively (possibly with
             * PA-FX-FAST or a wrong PA-ENC-TIMESTAMP).  The KDC must always
             * reply with ETYPE-INFO2 so the client learns the correct salt
             * and enctype before retrying.
             * 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;
            auto etypeInfo = Asn1Der::encodeEtypeInfo2(selectedEtype, salt);
@@ -3460,10 +3492,24 @@ bool Krb5KDC::lookupUser(AuthBackend &backend, const Krb5Principal &principal,
                    size_t upos = sizeof(AuthHeader);
                    user.info(backend, udat, upos);

                    /*
                     * 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;
                }
            }
+18 −0
Changes for src/user.cpp: 18 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ authdb::UserData::UserData(const uuid::uuid &id) : AuthData(id){
    lastname=nullptr;
    mail=nullptr;
    pwhash=nullptr;
    krbkey=nullptr;
    avatar=nullptr;
    Data.type=DataType::UserData;
}
@@ -119,6 +120,23 @@ const std::string authdb::UserData::getPwHash(){
    }
}

void authdb::UserData::setKrbKey(const std::string &src){
    setAuthData<char>(DataType::UserData,StorageType::TextStorage,this,&krbkey,"krbkey",src.c_str(),src.length()+1);
}

const std::string authdb::UserData::getKrbKey(){
    try{
        const char *res=getAuthData<char>(DataType::UserData,StorageType::TextStorage,this,&krbkey,"krbkey");
        if(res)
            return res;
        else
            return "";
    }catch(AuthBackendError &e){
        std::cerr << e.what() << std::endl;
        return "";
    }
}


void authdb::UserData::setUserName(const std::string &src){
    setAuthData<char>(DataType::UserData,StorageType::TextStorage,this,&username,"username",src.c_str(),src.length()+1);
+4 −0
Changes for src/user.h: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -63,6 +63,9 @@ namespace authdb {
        void setPwHash(const std::string &src);
        const std::string getPwHash();

        void setKrbKey(const std::string &src);
        const std::string getKrbKey();

        void setAvatar(const std::vector<char> &src);
        void getAvatar(std::vector<char> &dest);

@@ -72,6 +75,7 @@ namespace authdb {
        AuthData::Record* lastname;
        AuthData::Record* mail;
        AuthData::Record* pwhash;
        AuthData::Record* krbkey;
        //optional
        AuthData::Record* avatar;
        friend class User;