/*******************************************************************************
 * Copyright (c) 2025, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 *      names of its contributors may be used to endorse or promote products
 *      derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#include "pkcs12.h"
#include "sha.h"
#include "des.h"
#include "aes.h"

#include <fstream>
#include <cstring>
#include <stdexcept>
#include <algorithm>
#include <iostream>

namespace {

// ---- Minimal ASN.1 DER helpers (local to this TU) ----

struct DerTag {
    uint8_t     tag;
    const uint8_t* data;
    size_t      len;
    size_t      headerLen;   // bytes consumed for tag+length
};

static bool derRead(const uint8_t* buf, size_t bufLen, DerTag& out) {
    if (bufLen < 2) return false;
    out.tag = buf[0];
    size_t cursor = 1;
    size_t length = buf[cursor];
    cursor++;
    if (length & 0x80) {
        int numOctets = length & 0x7F;
        if (numOctets < 1 || numOctets > 4) return false;
        if (cursor + numOctets > bufLen) return false;
        length = 0;
        for (int i = 0; i < numOctets; ++i)
            length = (length << 8) | buf[cursor++];
    }
    if (cursor + length > bufLen) return false;
    out.data = buf + cursor;
    out.len  = length;
    out.headerLen = cursor;
    return true;
}

static size_t derTotalLen(const DerTag& t) { return t.headerLen + t.len; }

// Iterate children of a CONSTRUCTED tag
struct DerIter {
    const uint8_t* pos;
    const uint8_t* end;
    bool next(DerTag& out) {
        if (pos >= end) return false;
        if (!derRead(pos, end - pos, out)) return false;
        pos += derTotalLen(out);
        return true;
    }
};

static DerIter derChildren(const DerTag& parent) {
    return {parent.data, parent.data + parent.len};
}

// Compare OID bytes (tag 0x06)
static bool oidEq(const DerTag& t, const uint8_t* oid, size_t oidLen) {
    if (t.tag != 0x06) return false;
    if (t.len != oidLen) return false;
    return std::memcmp(t.data, oid, oidLen) == 0;
}

// -- Well-known OIDs --
// data (1.2.840.113549.1.7.1)
static const uint8_t OID_DATA[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x07,0x01};
// encryptedData (1.2.840.113549.1.7.6)
static const uint8_t OID_ENCRYPTED_DATA[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x07,0x06};
// pkcs8ShroudedKeyBag (1.2.840.113549.1.12.10.1.2)
static const uint8_t OID_SHROUDED_KEY_BAG[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x0c,0x0a,0x01,0x02};
// keyBag (1.2.840.113549.1.12.10.1.1)
static const uint8_t OID_KEY_BAG[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x0c,0x0a,0x01,0x01};
// certBag (1.2.840.113549.1.12.10.1.3)
static const uint8_t OID_CERT_BAG[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x0c,0x0a,0x01,0x03};
// x509Certificate (1.2.840.113549.1.9.22.1)
static const uint8_t OID_X509_CERT[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x09,0x16,0x01};
// pbeWithSHAAnd3-KeyTripleDES-CBC (1.2.840.113549.1.12.1.3)
static const uint8_t OID_PBE_3DES[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x0c,0x01,0x03};
// pbeWithSHAAnd40BitRC2-CBC (1.2.840.113549.1.12.1.6)
static const uint8_t OID_PBE_RC2[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x0c,0x01,0x06};
// PBES2 (1.2.840.113549.1.5.13)
static const uint8_t OID_PBES2[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x05,0x0d};
// PBKDF2 (1.2.840.113549.1.5.12)
static const uint8_t OID_PBKDF2[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x05,0x0c};
// hmacWithSHA256 (1.2.840.113549.2.9)
static const uint8_t OID_HMAC_SHA256[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x09};
// hmacWithSHA1 (1.2.840.113549.2.7)
static const uint8_t OID_HMAC_SHA1[] = {0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x07};
// aes-256-cbc (2.16.840.1.101.3.4.1.42)
static const uint8_t OID_AES256_CBC[] = {0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x01,0x2a};
// aes-128-cbc (2.16.840.1.101.3.4.1.2)
static const uint8_t OID_AES128_CBC[] = {0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x01,0x02};

// ---- PKCS#12 KDF (RFC 7292 Appendix B) ----
// id: 1 = key, 2 = IV, 3 = MAC key
static std::vector<uint8_t> pkcs12KDF(const std::string& password,
                                       const std::vector<uint8_t>& salt,
                                       uint8_t id, uint32_t iterations,
                                       size_t dkLen) {
    const size_t u = 20;  // SHA-1 output length
    const size_t v = 64;  // SHA-1 block size

    // Construct D = id repeated v times
    std::vector<uint8_t> D(v, id);

    // Construct S (salt padded to multiple of v)
    std::vector<uint8_t> S;
    if (!salt.empty()) {
        size_t sLen = v * ((salt.size() + v - 1) / v);
        S.resize(sLen);
        for (size_t i = 0; i < sLen; i++)
            S[i] = salt[i % salt.size()];
    }

    // Construct P (password as BMPString padded to multiple of v)
    std::vector<uint8_t> P;
    if (!password.empty()) {
        // Convert to BMPString (UTF-16BE with null terminator)
        std::vector<uint8_t> bmpPass;
        for (char c : password) {
            bmpPass.push_back(0x00);
            bmpPass.push_back(static_cast<uint8_t>(c));
        }
        bmpPass.push_back(0x00);
        bmpPass.push_back(0x00);

        size_t pLen = v * ((bmpPass.size() + v - 1) / v);
        P.resize(pLen);
        for (size_t i = 0; i < pLen; i++)
            P[i] = bmpPass[i % bmpPass.size()];
    }

    // I = S || P
    std::vector<uint8_t> I;
    I.reserve(S.size() + P.size());
    I.insert(I.end(), S.begin(), S.end());
    I.insert(I.end(), P.begin(), P.end());

    std::vector<uint8_t> result;
    result.reserve(dkLen);

    while (result.size() < dkLen) {
        // A = Hash(D || I)
        std::vector<uint8_t> AI;
        AI.reserve(D.size() + I.size());
        AI.insert(AI.end(), D.begin(), D.end());
        AI.insert(AI.end(), I.begin(), I.end());

        std::vector<uint8_t> A = netplus::sha1_hash(AI);

        for (uint32_t j = 1; j < iterations; j++)
            A = netplus::sha1_hash(A);

        result.insert(result.end(), A.begin(), A.end());

        if (result.size() >= dkLen) break;

        // Construct B = A repeated to v bytes
        std::vector<uint8_t> B(v);
        for (size_t i = 0; i < v; i++)
            B[i] = A[i % u];

        // I_j = (I_j + B + 1) mod 2^(v*8)
        for (size_t i = 0; i < I.size(); i += v) {
            uint16_t carry = 1;
            for (int k = (int)v - 1; k >= 0; k--) {
                uint16_t sum = (uint16_t)I[i + k] + (uint16_t)B[k] + carry;
                I[i + k] = (uint8_t)(sum & 0xFF);
                carry = sum >> 8;
            }
        }
    }

    result.resize(dkLen);
    return result;
}

// ---- 3DES-CBC decrypt ----
static std::vector<uint8_t> tdesDecryptCBC(const std::vector<uint8_t>& key24,
                                            const std::vector<uint8_t>& iv8,
                                            const std::vector<uint8_t>& ciphertext) {
    if (key24.size() != 24 || iv8.size() != 8) return {};
    if (ciphertext.empty() || (ciphertext.size() % 8) != 0) return {};

    netplus::block64 k1(key24.begin(),      key24.begin() + 8);
    netplus::block64 k2(key24.begin() + 8,  key24.begin() + 16);
    netplus::block64 k3(key24.begin() + 16, key24.begin() + 24);
    netplus::trippledes tdes(k1, k2, k3);

    std::vector<uint8_t> plaintext;
    plaintext.reserve(ciphertext.size());

    netplus::block64 prev(iv8.begin(), iv8.end());

    for (size_t off = 0; off < ciphertext.size(); off += 8) {
        netplus::block64 block(ciphertext.begin() + off, ciphertext.begin() + off + 8);
        netplus::block64 dec = tdes.decrypt(block);
        for (size_t i = 0; i < 8; i++)
            dec[i] ^= prev[i];
        plaintext.insert(plaintext.end(), dec.begin(), dec.end());
        prev = block;
    }

    // Remove PKCS#7 padding
    if (!plaintext.empty()) {
        uint8_t pad = plaintext.back();
        if (pad >= 1 && pad <= 8) {
            bool valid = true;
            for (size_t i = plaintext.size() - pad; i < plaintext.size(); i++) {
                if (plaintext[i] != pad) { valid = false; break; }
            }
            if (valid)
                plaintext.resize(plaintext.size() - pad);
        }
    }

    return plaintext;
}

// ---- AES-CBC decrypt with PKCS#7 unpadding ----
static std::vector<uint8_t> aesDecryptCBC(const std::vector<uint8_t>& key,
                                           const std::vector<uint8_t>& iv16,
                                           const std::vector<uint8_t>& ciphertext) {
    if (iv16.size() != 16) return {};
    if (ciphertext.empty() || (ciphertext.size() % 16) != 0) return {};

    std::vector<uint8_t> plaintext;
    if (key.size() == 32) {
        netplus::aes256 cipher(key);
        plaintext = cipher.decryptCBC(ciphertext, iv16);
    } else if (key.size() == 16) {
        netplus::aes128 cipher(key);
        plaintext = cipher.decryptCBC(ciphertext, iv16);
    } else {
        return {};
    }

    // Remove PKCS#7 padding
    if (!plaintext.empty()) {
        uint8_t pad = plaintext.back();
        if (pad >= 1 && pad <= 16) {
            bool valid = true;
            for (size_t i = plaintext.size() - pad; i < plaintext.size(); i++) {
                if (plaintext[i] != pad) { valid = false; break; }
            }
            if (valid)
                plaintext.resize(plaintext.size() - pad);
        }
    }

    return plaintext;
}

// ---- Decrypt using legacy PKCS#12 PBE (pbeWithSHAAnd3-KeyTripleDES-CBC) ----
static std::vector<uint8_t> decryptPBE_3DES(const std::vector<uint8_t>& salt,
                                             uint32_t iterations,
                                             const std::string& password,
                                             const std::vector<uint8_t>& ciphertext) {
    auto key = pkcs12KDF(password, salt, 1, iterations, 24);
    auto iv  = pkcs12KDF(password, salt, 2, iterations, 8);
    return tdesDecryptCBC(key, iv, ciphertext);
}

// ---- Decrypt using PBES2 (PBKDF2 + AES-CBC) ----
// Parses the PBES2 AlgorithmIdentifier parameters
static std::vector<uint8_t> decryptPBES2(const DerTag& params,
                                          const std::string& password,
                                          const std::vector<uint8_t>& ciphertext) {
    // params = SEQUENCE { keyDerivationFunc, encryptionScheme }
    DerIter pit = derChildren(params);
    DerTag kdfSeq, encSeq;
    if (!pit.next(kdfSeq) || !pit.next(encSeq)) return {};

    // kdfSeq = SEQUENCE { OID(PBKDF2), SEQUENCE { salt, iterations, [keyLength], prf } }
    DerIter kdfIt = derChildren(kdfSeq);
    DerTag kdfOid;
    if (!kdfIt.next(kdfOid)) return {};
    if (!oidEq(kdfOid, OID_PBKDF2, sizeof(OID_PBKDF2))) return {};

    DerTag kdfParams;
    if (!kdfIt.next(kdfParams)) return {};

    DerIter kpIt = derChildren(kdfParams);
    DerTag saltTag, iterTag;
    if (!kpIt.next(saltTag)) return {};   // OCTET STRING salt
    if (!kpIt.next(iterTag)) return {};   // INTEGER iterations

    std::vector<uint8_t> salt(saltTag.data, saltTag.data + saltTag.len);
    uint32_t iterations = 0;
    for (size_t i = 0; i < iterTag.len; i++)
        iterations = (iterations << 8) | iterTag.data[i];

    // Optional: keyLength, then PRF
    // Determine PRF (default HMAC-SHA1)
    bool useSha256 = false;
    DerTag nextTag;
    while (kpIt.next(nextTag)) {
        if (nextTag.tag == 0x30) { // SEQUENCE = AlgorithmIdentifier for PRF
            DerIter prfIt = derChildren(nextTag);
            DerTag prfOid;
            if (prfIt.next(prfOid)) {
                if (oidEq(prfOid, OID_HMAC_SHA256, sizeof(OID_HMAC_SHA256)))
                    useSha256 = true;
            }
        }
    }

    // encSeq = SEQUENCE { OID(aes-xxx-cbc), OCTET STRING iv }
    DerIter encIt = derChildren(encSeq);
    DerTag encOid, ivTag;
    if (!encIt.next(encOid)) return {};
    if (!encIt.next(ivTag)) return {};

    std::vector<uint8_t> iv(ivTag.data, ivTag.data + ivTag.len);
    size_t keyLen = 0;
    if (oidEq(encOid, OID_AES256_CBC, sizeof(OID_AES256_CBC)))
        keyLen = 32;
    else if (oidEq(encOid, OID_AES128_CBC, sizeof(OID_AES128_CBC)))
        keyLen = 16;
    else
        return {};  // unsupported cipher

    std::vector<uint8_t> pw(password.begin(), password.end());
    std::vector<uint8_t> dk;
    if (useSha256)
        dk = netplus::pbkdf2_hmac_sha256(pw, salt, iterations, keyLen);
    else
        dk = netplus::pbkdf2_hmac_sha1(pw, salt, iterations, keyLen);

    return aesDecryptCBC(dk, iv, ciphertext);
}

// Generic decryption dispatcher based on algorithm OID
static std::vector<uint8_t> decryptByAlg(const DerTag& algSeq,
                                          const std::string& password,
                                          const std::vector<uint8_t>& ciphertext) {
    DerIter aIt = derChildren(algSeq);
    DerTag algOid;
    if (!aIt.next(algOid)) return {};

    if (oidEq(algOid, OID_PBE_3DES, sizeof(OID_PBE_3DES))) {
        // pbeWithSHAAnd3-KeyTripleDES-CBC
        DerTag pbeParams;
        if (!aIt.next(pbeParams)) return {};
        DerIter ppIt = derChildren(pbeParams);
        DerTag saltTag, iterTag;
        if (!ppIt.next(saltTag)) return {};
        if (!ppIt.next(iterTag)) return {};

        std::vector<uint8_t> salt(saltTag.data, saltTag.data + saltTag.len);
        uint32_t iterations = 0;
        for (size_t i = 0; i < iterTag.len; i++)
            iterations = (iterations << 8) | iterTag.data[i];

        return decryptPBE_3DES(salt, iterations, password, ciphertext);
    }

    if (oidEq(algOid, OID_PBE_RC2, sizeof(OID_PBE_RC2))) {
        // pbeWithSHAAnd40BitRC2-CBC -- use same PKCS12 KDF with 5-byte key
        // RC2 not implemented, fall through
        return {};
    }

    if (oidEq(algOid, OID_PBES2, sizeof(OID_PBES2))) {
        DerTag pbes2Params;
        if (!aIt.next(pbes2Params)) return {};
        return decryptPBES2(pbes2Params, password, ciphertext);
    }

    return {};
}

// Parse SafeContents (sequence of SafeBag), extract certs and keys
static bool parseSafeContents(const uint8_t* data, size_t len,
                               const std::string& password,
                               std::vector<std::vector<uint8_t>>& certs,
                               std::vector<uint8_t>& keyDer) {
    DerTag safeContents;
    if (!derRead(data, len, safeContents)) return false;

    DerIter sIt = derChildren(safeContents);
    DerTag safeBag;
    while (sIt.next(safeBag)) {
        if (safeBag.tag != 0x30) continue;
        DerIter bIt = derChildren(safeBag);
        DerTag bagOid, bagValue;
        if (!bIt.next(bagOid)) continue;
        if (!bIt.next(bagValue)) continue;

        // bagValue is [0] EXPLICIT — unwrap to get inner content
        const uint8_t* valData = bagValue.data;
        size_t valLen = bagValue.len;

        if (oidEq(bagOid, OID_CERT_BAG, sizeof(OID_CERT_BAG))) {
            // CertBag = SEQUENCE { certId OID, certValue [0] EXPLICIT OCTET STRING }
            DerTag certBagSeq;
            if (!derRead(valData, valLen, certBagSeq)) continue;
            DerIter cbIt = derChildren(certBagSeq);
            DerTag certType, certVal;
            if (!cbIt.next(certType)) continue;
            if (!cbIt.next(certVal)) continue;

            if (oidEq(certType, OID_X509_CERT, sizeof(OID_X509_CERT))) {
                // certVal is [0] EXPLICIT wrapping OCTET STRING
                DerTag octetStr;
                if (derRead(certVal.data, certVal.len, octetStr)) {
                    if (octetStr.tag == 0x04) {
                        certs.emplace_back(octetStr.data, octetStr.data + octetStr.len);
                    } else if (octetStr.tag == 0x30) {
                        // Some exporters put the DER cert directly
                        certs.emplace_back(certVal.data, certVal.data + certVal.len);
                    }
                }
            }
        }
        else if (oidEq(bagOid, OID_SHROUDED_KEY_BAG, sizeof(OID_SHROUDED_KEY_BAG))) {
            // PKCS8ShroudedKeyBag = EncryptedPrivateKeyInfo
            DerTag encPki;
            if (!derRead(valData, valLen, encPki)) continue;

            DerIter epIt = derChildren(encPki);
            DerTag algSeq, encDataTag;
            if (!epIt.next(algSeq)) continue;
            if (!epIt.next(encDataTag)) continue;

            std::vector<uint8_t> encData(encDataTag.data, encDataTag.data + encDataTag.len);
            auto decrypted = decryptByAlg(algSeq, password, encData);
            if (!decrypted.empty())
                keyDer = std::move(decrypted);
        }
        else if (oidEq(bagOid, OID_KEY_BAG, sizeof(OID_KEY_BAG))) {
            // keyBag = unencrypted PKCS#8 PrivateKeyInfo
            DerTag pkiSeq;
            if (!derRead(valData, valLen, pkiSeq)) continue;
            keyDer.assign(pkiSeq.data - pkiSeq.headerLen,
                          pkiSeq.data + pkiSeq.len);
        }
    }
    return true;
}

} // anonymous namespace

// ---- Public API ----

bool netplus::pkcs12Parse(const std::vector<uint8_t>& pfxData,
                           const std::string& password,
                           Pkcs12Result& out) {
    // PFX = SEQUENCE { version INTEGER, authSafe ContentInfo, macData MacData OPTIONAL }
    DerTag pfx;
    if (!derRead(pfxData.data(), pfxData.size(), pfx)) return false;
    if (pfx.tag != 0x30) return false;

    DerIter pfxIt = derChildren(pfx);
    DerTag versionTag;
    if (!pfxIt.next(versionTag)) return false;
    // version should be 3

    // authSafe ContentInfo
    DerTag authSafe;
    if (!pfxIt.next(authSafe)) return false;

    DerIter asIt = derChildren(authSafe);
    DerTag contentType;
    if (!asIt.next(contentType)) return false;
    if (!oidEq(contentType, OID_DATA, sizeof(OID_DATA))) return false;

    // [0] EXPLICIT OCTET STRING
    DerTag explicit0;
    if (!asIt.next(explicit0)) return false;
    DerTag octetStr;
    if (!derRead(explicit0.data, explicit0.len, octetStr)) return false;

    // AuthenticatedSafe = SEQUENCE OF ContentInfo
    DerTag authSafeSeq;
    if (!derRead(octetStr.data, octetStr.len, authSafeSeq)) return false;

    std::vector<std::vector<uint8_t>> allCerts;
    std::vector<uint8_t> keyDer;

    DerIter asSeqIt = derChildren(authSafeSeq);
    DerTag contentInfo;
    while (asSeqIt.next(contentInfo)) {
        if (contentInfo.tag != 0x30) continue;

        DerIter ciIt = derChildren(contentInfo);
        DerTag ciType;
        if (!ciIt.next(ciType)) continue;

        DerTag ciContent;
        if (!ciIt.next(ciContent)) continue;

        if (oidEq(ciType, OID_DATA, sizeof(OID_DATA))) {
            // Unencrypted data: [0] EXPLICIT OCTET STRING containing SafeContents
            DerTag innerOctet;
            if (!derRead(ciContent.data, ciContent.len, innerOctet)) continue;

            parseSafeContents(innerOctet.data, innerOctet.len,
                             password, allCerts, keyDer);
        }
        else if (oidEq(ciType, OID_ENCRYPTED_DATA, sizeof(OID_ENCRYPTED_DATA))) {
            // Encrypted data
            // [0] EXPLICIT EncryptedData
            DerTag encData;
            if (!derRead(ciContent.data, ciContent.len, encData)) continue;

            // EncryptedData = SEQUENCE { version, EncryptedContentInfo }
            DerIter edIt = derChildren(encData);
            DerTag edVersion;
            if (!edIt.next(edVersion)) continue;

            // EncryptedContentInfo = SEQUENCE { contentType, contentEncAlg, [0] encContent }
            DerTag encContentInfo;
            if (!edIt.next(encContentInfo)) continue;

            DerIter eciIt = derChildren(encContentInfo);
            DerTag eciType, eciAlg, eciEncContent;
            if (!eciIt.next(eciType)) continue;
            if (!eciIt.next(eciAlg)) continue;
            if (!eciIt.next(eciEncContent)) continue;

            // eciEncContent is [0] IMPLICIT OCTET STRING
            std::vector<uint8_t> ciphertext(eciEncContent.data, eciEncContent.data + eciEncContent.len);
            auto decrypted = decryptByAlg(eciAlg, password, ciphertext);
            if (!decrypted.empty()) {
                parseSafeContents(decrypted.data(), decrypted.size(),
                                 password, allCerts, keyDer);
            }
        }
    }

    // Populate result: first cert = main, rest = chain
    if (!allCerts.empty()) {
        out.certDer = std::move(allCerts[0]);
        for (size_t i = 1; i < allCerts.size(); i++)
            out.chainDer.push_back(std::move(allCerts[i]));
    }
    out.keyDer = std::move(keyDer);

    if (out.certDer.empty())
        std::cerr << "pkcs12Parse: no certificate found in P12" << std::endl;
    if (out.keyDer.empty())
        std::cerr << "pkcs12Parse: no private key found in P12" << std::endl;

    return !out.certDer.empty() || !out.keyDer.empty();
}

bool netplus::pkcs12ParseFile(const std::string& path,
                               const std::string& password,
                               Pkcs12Result& out) {
    std::ifstream file(path, std::ios::binary | std::ios::ate);
    if (!file.is_open()) return false;

    std::streamsize size = file.tellg();
    if (size <= 0) return false;

    file.seekg(0, std::ios::beg);
    std::vector<uint8_t> data(static_cast<size_t>(size));
    if (!file.read(reinterpret_cast<char*>(data.data()), size)) return false;

    return pkcs12Parse(data, password, out);
}
