Loading src/krb5.cpp +156 −143 Changes for src/krb5.cpp: 156 added lines, 143 removed lines. Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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); Loading @@ -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); Loading @@ -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); Loading @@ -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, Loading Loading @@ -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; Loading @@ -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, Loading Loading
src/krb5.cpp +156 −143 Changes for src/krb5.cpp: 156 added lines, 143 removed lines. Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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); Loading @@ -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); Loading @@ -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); Loading @@ -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, Loading Loading @@ -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; Loading @@ -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, Loading