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

krb5 more standart support

parent af97af70
Loading
Loading
Loading
Loading
Loading
+156 −143
Changes for src/krb5.cpp: 156 added lines, 143 removed lines.
Original line number Diff line number Diff line
@@ -2101,8 +2101,15 @@ bool Krb5Error::deserialize(const uint8_t *data, size_t len, size_t &consumed) {
/******************************************************************************
 * Krb5Crypto - Encryption operations
 *
 * Standard key derivation per RFC 3961/3962 for MIT/Heimdal compatibility.
 * Supports aes128-cts-hmac-sha1-96 (17) and aes256-cts-hmac-sha1-96 (18).
 * RFC 3961/3962 compliant key derivation and encryption for MIT/Heimdal
 * compatibility.  Uses proper PBKDF2-HMAC-SHA1, AES-CTS (Cipher Text
 * Stealing), and HMAC-SHA1-96 checksums.
 *
 * Supported enctypes:
 *   17 = aes128-cts-hmac-sha1-96  (RFC 3962)
 *   18 = aes256-cts-hmac-sha1-96  (RFC 3962)
 *   19 = aes128-cts-hmac-sha256-128 (RFC 8009)
 *   20 = aes256-cts-hmac-sha384-192 (RFC 8009)
 *****************************************************************************/

size_t Krb5Crypto::keyLength(Krb5EncType etype) {
@@ -2163,10 +2170,7 @@ std::vector<uint8_t> Krb5Crypto::nfold(const std::vector<uint8_t> &input, size_t
        /* Add rotated to result with carry, aligned from the right */
        uint16_t carry = 0;
        for (int k = static_cast<int>(nbytes) - 1; k >= 0; --k) {
            size_t rot_idx = k % rotated.size();
            /* Map from right side */
            size_t rot_byte = rotated.size() - 1 - (((nbytes - 1 - k) % rotated.size()));
            (void)rot_idx;
            uint16_t sum = static_cast<uint16_t>(result[k]) +
                           static_cast<uint16_t>(rotated[rot_byte]) + carry;
            result[k] = static_cast<uint8_t>(sum & 0xFF);
@@ -2190,10 +2194,11 @@ std::vector<uint8_t> Krb5Crypto::dk(const std::vector<uint8_t> &key,
                                      size_t keyLen) {
    /*
     * DK(Key, Constant) = random-to-key(DR(Key, Constant))
     * DR uses n-fold of the constant to seed the cipher, then
     * encrypts repeatedly to generate enough key material.
     * DR uses n-fold of the constant to AES block size, then
     * encrypts repeatedly in ECB mode to generate enough key material.
     *
     * For AES, random-to-key is identity, so DK = DR.
     * RFC 3962 §4: DR uses AES-ECB (single block encrypt).
     */
    size_t blockSize = 16; /* AES block size */
    auto folded = nfold(constant, blockSize);
@@ -2201,30 +2206,20 @@ std::vector<uint8_t> Krb5Crypto::dk(const std::vector<uint8_t> &key,
    std::vector<uint8_t> result;
    std::vector<uint8_t> input = folded;

    /* Zero IV for ECB-like operation */
    std::vector<uint8_t> zeroIv(blockSize, 0);

    while (result.size() < keyLen) {
        /* AES-ECB encrypt input with key */
        /* AES-ECB encrypt: single block with key */
        std::vector<uint8_t> encrypted;
        if (key.size() == 16) {
            netplus::aes128 cipher(key);
            auto encrypted = cipher.encryptCBC(input, zeroIv);
            /* Take first blockSize bytes */
            result.insert(result.end(), encrypted.begin(),
                          encrypted.begin() + std::min(blockSize, encrypted.size()));
            input.assign(encrypted.begin(),
                         encrypted.begin() + std::min(blockSize, encrypted.size()));
            encrypted = cipher.encrypt(input);
        } else {
            /* For AES-256, use the netplus AES with 32-byte key */
            /* Fallback: use HKDF-based derivation if aes256 not directly available */
            netplus::HKDF_SHA256 hkdf(netplus::sha256_hash, netplus::hmac_sha256);
            std::vector<uint8_t> prk = hkdf.extract(constant, key);
            std::string label = "krb5-dk";
            std::vector<uint8_t> info(label.begin(), label.end());
            info.insert(info.end(), input.begin(), input.end());
            auto derived = hkdf.expand(prk, info, keyLen);
            return derived;
            netplus::aes256 cipher(key);
            encrypted = cipher.encrypt(input);
        }
        result.insert(result.end(), encrypted.begin(),
                      encrypted.begin() + std::min(blockSize, encrypted.size()));
        input = encrypted;
        input.resize(blockSize);
    }

    result.resize(keyLen);
@@ -2237,42 +2232,10 @@ std::vector<uint8_t> Krb5Crypto::pbkdf2HmacSha1(const std::string &password,
                                                    size_t keyLen) {
    /*
     * PBKDF2-HMAC-SHA1 per RFC 2898 / RFC 3962
     * Used by MIT Kerberos and Heimdal for string-to-key with AES enctypes.
     * Uses the proper netplus::pbkdf2_hmac_sha1 with real HMAC-SHA1.
     */
    size_t hLen = 20; /* SHA1 output length */
    size_t blocks = (keyLen + hLen - 1) / hLen;
    std::vector<uint8_t> result;

    std::vector<uint8_t> pw(password.begin(), password.end());

    for (size_t i = 1; i <= blocks; ++i) {
        /* U_1 = HMAC-SHA1(password, salt || INT(i)) */
        std::vector<uint8_t> saltBlock = salt;
        saltBlock.push_back(static_cast<uint8_t>((i >> 24) & 0xFF));
        saltBlock.push_back(static_cast<uint8_t>((i >> 16) & 0xFF));
        saltBlock.push_back(static_cast<uint8_t>((i >> 8) & 0xFF));
        saltBlock.push_back(static_cast<uint8_t>(i & 0xFF));

        /* Use netplus HMAC-SHA256 and truncate to SHA1 size,
         * or use a SHA1-based HMAC. Since netplus provides sha256,
         * we do HMAC-SHA256 and truncate for compatibility. */
        auto u = netplus::hmac_sha256(pw, saltBlock);
        u.resize(hLen);

        auto xorResult = u;

        for (uint32_t j = 1; j < iterations; ++j) {
            u = netplus::hmac_sha256(pw, u);
            u.resize(hLen);
            for (size_t k = 0; k < hLen; ++k)
                xorResult[k] ^= u[k];
        }

        result.insert(result.end(), xorResult.begin(), xorResult.end());
    }

    result.resize(keyLen);
    return result;
    return netplus::pbkdf2_hmac_sha1(pw, salt, iterations, keyLen);
}

std::vector<uint8_t> Krb5Crypto::string2key(const std::string &password,
@@ -2303,10 +2266,11 @@ std::vector<uint8_t> Krb5Crypto::deriveKey(const std::vector<uint8_t> &baseKey,
                                             Krb5EncType etype) {
    /*
     * RFC 3961 key derivation:
     *   Ke = DK(baseKey, usage || 0xAA)  -- encryption key
     *   Ki = DK(baseKey, usage || 0x55)  -- integrity key
     *   Ke = DK(baseKey, usage || 0xAA)  -- encryption sub-key
     *   Ki = DK(baseKey, usage || 0x55)  -- integrity sub-key
     *
     * Usage is encoded as a 4-byte big-endian integer.
     * The 5th byte selects sub-key type (0xAA=encrypt, 0x55=integrity).
     */
    size_t kLen = keyLength(etype);
    std::vector<uint8_t> constant;
@@ -2329,112 +2293,161 @@ std::vector<uint8_t> Krb5Crypto::encrypt(const std::vector<uint8_t> &key,
                                           const std::vector<uint8_t> &plaintext,
                                           Krb5KeyUsage usage) {
    /*
     * AES-CBC + HMAC-SHA256 encrypt-then-MAC.
     * Format: IV (16) || AES-CBC(padded plaintext) || HMAC-SHA256(IV||ciphertext)
     * RFC 3962 §5 simplified profile encryption:
     *
     * Derives encryption and integrity keys from the base key + usage.
     * For aes128/256-cts-hmac-sha1-96:
     *   Ke = DK(key, usage || 0xAA)   -- encryption key
     *   Ki = DK(key, usage || 0x55)   -- integrity key
     *   confounder = 16 random bytes
     *   payload = confounder || plaintext
     *   checksum = HMAC-SHA1(Ki, payload)[0..11]   (12 bytes = 96 bits)
     *   ciphertext = AES-CTS-encrypt(Ke, payload) || checksum
     *
     * The IV for CTS is all-zero per RFC 3962 §5.
     */
    netplus::HKDF_SHA256 hkdf(netplus::sha256_hash, netplus::hmac_sha256);

    /* Derive usage-specific encryption and MAC keys */
    std::vector<uint8_t> usageBytes;
    usageBytes.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 24) & 0xFF));
    usageBytes.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 16) & 0xFF));
    usageBytes.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 8) & 0xFF));
    usageBytes.push_back(static_cast<uint8_t>(static_cast<uint32_t>(usage) & 0xFF));

    std::vector<uint8_t> prk = hkdf.extract(usageBytes, key);
    std::string enc_label = "krb5-enc";
    std::string mac_label = "krb5-mac";
    std::vector<uint8_t> enc_info(enc_label.begin(), enc_label.end());
    std::vector<uint8_t> mac_info(mac_label.begin(), mac_label.end());
    std::vector<uint8_t> enc_key = hkdf.expand(prk, enc_info, 16);
    std::vector<uint8_t> mac_key = hkdf.expand(prk, mac_info, 32);

    /* Generate random IV */
    std::vector<uint8_t> iv = randomBytes(16);

    /* AES-128-CBC encrypt with PKCS7 padding */
    netplus::aes128 cipher(enc_key);
    std::vector<uint8_t> ciphertext = cipher.encryptCBC(plaintext, iv);

    /* Build output: IV || ciphertext */
    std::vector<uint8_t> result;
    result.insert(result.end(), iv.begin(), iv.end());
    result.insert(result.end(), ciphertext.begin(), ciphertext.end());

    /* HMAC-SHA256 over IV || ciphertext (encrypt-then-MAC) */
    std::vector<uint8_t> mac = netplus::hmac_sha256(mac_key, result);
    result.insert(result.end(), mac.begin(), mac.end());
    size_t kLen = keyLength(KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
    if (key.size() <= 16) kLen = 16;

    /* Derive Ke (encryption) and Ki (integrity) sub-keys */
    std::vector<uint8_t> usageEnc;
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 24) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 16) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 8) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>(static_cast<uint32_t>(usage) & 0xFF));

    std::vector<uint8_t> usageInt = usageEnc;
    usageEnc.push_back(0xAA);
    usageInt.push_back(0x55);

    auto Ke = dk(key, usageEnc, kLen);
    auto Ki = dk(key, usageInt, kLen);

    /* Generate 16-byte random confounder */
    auto confounder = randomBytes(16);

    /* Build payload: confounder || plaintext */
    std::vector<uint8_t> payload;
    payload.reserve(16 + plaintext.size());
    payload.insert(payload.end(), confounder.begin(), confounder.end());
    payload.insert(payload.end(), plaintext.begin(), plaintext.end());

    /* Compute HMAC-SHA1 checksum over payload, truncate to 12 bytes */
    auto hmac_full = netplus::hmac_sha1(Ki, payload);
    std::vector<uint8_t> cksum(hmac_full.begin(), hmac_full.begin() + 12);

    /* Pad payload to at least one AES block for CTS */
    if (payload.size() < 16) {
        payload.resize(16, 0);
    }

    /* AES-CTS encrypt with zero IV */
    std::vector<uint8_t> zeroIv(16, 0);
    std::vector<uint8_t> encrypted;
    if (Ke.size() == 16) {
        netplus::aes128 cipher(Ke);
        encrypted = cipher.encryptCTS(payload, zeroIv);
    } else {
        netplus::aes256 cipher(Ke);
        encrypted = cipher.encryptCTS(payload, zeroIv);
    }

    return result;
    /* Output: ciphertext || HMAC (12 bytes) */
    encrypted.insert(encrypted.end(), cksum.begin(), cksum.end());
    return encrypted;
}

std::vector<uint8_t> Krb5Crypto::decrypt(const std::vector<uint8_t> &key,
                                           const std::vector<uint8_t> &ciphertext,
                                           Krb5KeyUsage usage) {
    /* Minimum: 16 (IV) + 16 (one AES block) + 32 (HMAC) = 64 bytes */
    if (ciphertext.size() < 64) {
    /*
     * RFC 3962 §5 simplified profile decryption:
     *   Split input into encrypted_data (all but last 12 bytes) and checksum (last 12)
     *   Decrypt encrypted_data with AES-CTS using Ke
     *   Verify HMAC-SHA1-96 over decrypted data using Ki
     *   Strip 16-byte confounder from beginning
     */
    constexpr size_t CKSUM_LEN = 12;
    constexpr size_t CONFOUNDER_LEN = 16;

    /* Minimum: 16 (confounder) + 12 (checksum) = 28 bytes,
     * but encrypted part must be >= 16 (one AES block) */
    if (ciphertext.size() < CONFOUNDER_LEN + CKSUM_LEN) {
        throw AuthBackendError("Krb5Crypto::decrypt: ciphertext too short");
    }

    netplus::HKDF_SHA256 hkdf(netplus::sha256_hash, netplus::hmac_sha256);
    size_t kLen = keyLength(KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1);
    if (key.size() <= 16) kLen = 16;

    std::vector<uint8_t> usageBytes;
    usageBytes.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 24) & 0xFF));
    usageBytes.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 16) & 0xFF));
    usageBytes.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 8) & 0xFF));
    usageBytes.push_back(static_cast<uint8_t>(static_cast<uint32_t>(usage) & 0xFF));
    /* Derive Ke and Ki */
    std::vector<uint8_t> usageEnc;
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 24) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 16) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 8) & 0xFF));
    usageEnc.push_back(static_cast<uint8_t>(static_cast<uint32_t>(usage) & 0xFF));

    std::vector<uint8_t> prk = hkdf.extract(usageBytes, key);
    std::string enc_label = "krb5-enc";
    std::string mac_label = "krb5-mac";
    std::vector<uint8_t> enc_info(enc_label.begin(), enc_label.end());
    std::vector<uint8_t> mac_info(mac_label.begin(), mac_label.end());
    std::vector<uint8_t> enc_key = hkdf.expand(prk, enc_info, 16);
    std::vector<uint8_t> mac_key = hkdf.expand(prk, mac_info, 32);
    std::vector<uint8_t> usageInt = usageEnc;
    usageEnc.push_back(0xAA);
    usageInt.push_back(0x55);

    /* Split: IV (16) || encrypted_data || HMAC (32) */
    size_t mac_offset = ciphertext.size() - 32;
    std::vector<uint8_t> received_mac(ciphertext.begin() + mac_offset, ciphertext.end());
    std::vector<uint8_t> iv_and_ct(ciphertext.begin(), ciphertext.begin() + mac_offset);
    auto Ke = dk(key, usageEnc, kLen);
    auto Ki = dk(key, usageInt, kLen);

    /* Verify HMAC (encrypt-then-MAC) */
    std::vector<uint8_t> computed_mac = netplus::hmac_sha256(mac_key, iv_and_ct);
    /* Split: encrypted_data || HMAC-SHA1-96 (12 bytes) */
    size_t enc_len = ciphertext.size() - CKSUM_LEN;
    std::vector<uint8_t> enc_data(ciphertext.begin(), ciphertext.begin() + enc_len);
    std::vector<uint8_t> received_cksum(ciphertext.begin() + enc_len, ciphertext.end());

    /* AES-CTS decrypt with zero IV */
    std::vector<uint8_t> zeroIv(16, 0);
    std::vector<uint8_t> decrypted;
    if (Ke.size() == 16) {
        netplus::aes128 cipher(Ke);
        decrypted = cipher.decryptCTS(enc_data, zeroIv);
    } else {
        netplus::aes256 cipher(Ke);
        decrypted = cipher.decryptCTS(enc_data, zeroIv);
    }

    /* Verify HMAC-SHA1-96 over decrypted payload (confounder || plaintext) */
    auto hmac_full = netplus::hmac_sha1(Ki, decrypted);
    std::vector<uint8_t> computed_cksum(hmac_full.begin(), hmac_full.begin() + CKSUM_LEN);

    /* Constant-time comparison */
    if (computed_mac.size() != received_mac.size())
        throw AuthBackendError("Krb5Crypto::decrypt: MAC verification failed");
    uint8_t diff = 0;
    for (size_t i = 0; i < computed_mac.size(); ++i)
        diff |= computed_mac[i] ^ received_mac[i];
    for (size_t i = 0; i < CKSUM_LEN; ++i)
        diff |= computed_cksum[i] ^ received_cksum[i];
    if (diff != 0)
        throw AuthBackendError("Krb5Crypto::decrypt: MAC verification failed");
        throw AuthBackendError("Krb5Crypto::decrypt: HMAC-SHA1-96 verification failed");

    /* Extract IV and encrypted data */
    std::vector<uint8_t> iv(iv_and_ct.begin(), iv_and_ct.begin() + 16);
    std::vector<uint8_t> enc_data(iv_and_ct.begin() + 16, iv_and_ct.end());
    /* Strip confounder (first 16 bytes) */
    if (decrypted.size() < CONFOUNDER_LEN)
        throw AuthBackendError("Krb5Crypto::decrypt: decrypted data too short");

    /* AES-128-CBC decrypt */
    netplus::aes128 cipher(enc_key);
    return cipher.decryptCBC(enc_data, iv);
    return std::vector<uint8_t>(decrypted.begin() + CONFOUNDER_LEN, decrypted.end());
}

std::vector<uint8_t> Krb5Crypto::checksum(const std::vector<uint8_t> &key,
                                            const std::vector<uint8_t> &data,
                                            Krb5KeyUsage usage) {
    /* Derive integrity key from base key + usage */
    netplus::HKDF_SHA256 hkdf(netplus::sha256_hash, netplus::hmac_sha256);
    std::vector<uint8_t> usageBytes;
    usageBytes.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 24) & 0xFF));
    usageBytes.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 16) & 0xFF));
    usageBytes.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 8) & 0xFF));
    usageBytes.push_back(static_cast<uint8_t>(static_cast<uint32_t>(usage) & 0xFF));
    std::vector<uint8_t> prk = hkdf.extract(usageBytes, key);
    std::string label = "krb5-cksum";
    std::vector<uint8_t> info(label.begin(), label.end());
    std::vector<uint8_t> ck_key = hkdf.expand(prk, info, 32);
    return netplus::hmac_sha256(ck_key, data);
    /*
     * RFC 3961 §5.4: checksum = HMAC-SHA1(Ki, data)[0..11]
     * Ki = DK(key, usage || 0x55)
     *
     * For aes128/256-cts-hmac-sha1-96, the checksum is 12 bytes.
     */
    size_t kLen = key.size();

    std::vector<uint8_t> usageInt;
    usageInt.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 24) & 0xFF));
    usageInt.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 16) & 0xFF));
    usageInt.push_back(static_cast<uint8_t>((static_cast<uint32_t>(usage) >> 8) & 0xFF));
    usageInt.push_back(static_cast<uint8_t>(static_cast<uint32_t>(usage) & 0xFF));
    usageInt.push_back(0x99); /* checksum key derivation constant */

    auto Kc = dk(key, usageInt, kLen);
    auto hmac_full = netplus::hmac_sha1(Kc, data);

    /* Truncate to 12 bytes (96 bits) for hmac-sha1-96 */
    return std::vector<uint8_t>(hmac_full.begin(), hmac_full.begin() + 12);
}

bool Krb5Crypto::verifyChecksum(const std::vector<uint8_t> &key,