/* RSP Cryptographic Operations Implementation * * Author: Eric Wild / sysmocom - s.f.m.c. GmbH * * Released under the terms of GNU General Public License, Version 2 or * (at your option) any later version. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include } #include "rsp_client.h" #include "http_client.h" #include "logger.h" namespace RspCrypto { // Private helper functions for CertificateUtil static std::unique_ptr readFileToBIO(const std::string& filePath) { std::ifstream file(filePath); if (!file) { throw std::runtime_error("Failed to open file: " + filePath); } std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); std::unique_ptr bio(BIO_new_mem_buf(content.c_str(), -1)); if (!bio) { throw OpenSSLError("Failed to create BIO from file content"); } return bio; } static std::vector extractPublicKeyData(EVP_PKEY* key) { size_t pub_len = 0; if (EVP_PKEY_get_octet_string_param(key, OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &pub_len) != 1) { throw OpenSSLError("Failed to get public key size"); } std::vector publicKeyData(pub_len); if (EVP_PKEY_get_octet_string_param(key, OSSL_PKEY_PARAM_PUB_KEY, publicKeyData.data(), pub_len, &pub_len) != 1) { throw OpenSSLError("Failed to extract public key"); } return publicKeyData; } static std::vector getCertificateExtension(X509* cert, int nid) { int loc = X509_get_ext_by_NID(cert, nid, -1); if (loc < 0) { return std::vector(); } X509_EXTENSION* ext = X509_get_ext(cert, loc); if (!ext) { return std::vector(); } ASN1_OCTET_STRING* ext_data = X509_EXTENSION_get_data(ext); if (!ext_data) { return std::vector(); } return std::vector(ASN1_STRING_get0_data(ext_data), ASN1_STRING_get0_data(ext_data) + ASN1_STRING_length(ext_data)); } // CertificateUtil implementations std::string CertificateUtil::getEID(X509* cert) { X509_NAME* subject = X509_get_subject_name(cert); if (!subject) { return ""; } // EID is stored in serialNumber field int index = X509_NAME_get_index_by_NID(subject, NID_serialNumber, -1); if (index < 0) { return ""; } X509_NAME_ENTRY* entry = X509_NAME_get_entry(subject, index); if (!entry) { return ""; } ASN1_STRING* asn1_str = X509_NAME_ENTRY_get_data(entry); if (!asn1_str) { return ""; } const unsigned char* str_data = ASN1_STRING_get0_data(asn1_str); int str_len = ASN1_STRING_length(asn1_str); return std::string(reinterpret_cast(str_data), str_len); } std::string CertificateUtil::getSubjectName(X509* cert) { char subjectName[256]; X509_NAME_oneline(X509_get_subject_name(cert), subjectName, sizeof(subjectName)); return std::string(subjectName); } std::vector CertificateUtil::getSubjectKeyIdentifier(X509* cert) { auto ext_data = getCertificateExtension(cert, NID_subject_key_identifier); if (ext_data.empty()) { return std::vector(); } const unsigned char* p = ext_data.data(); long xlen = ext_data.size(); int tag, xclass; ASN1_get_object(&p, &xlen, &tag, &xclass, ext_data.size()); return std::vector(p, p + xlen); } std::vector CertificateUtil::getAuthorityKeyIdentifier(X509* cert) { auto ext_data = getCertificateExtension(cert, NID_authority_key_identifier); if (ext_data.empty()) { return std::vector(); } // AKI is more complex, need to extract keyid const unsigned char* p = ext_data.data(); AUTHORITY_KEYID* akid = d2i_AUTHORITY_KEYID(NULL, &p, ext_data.size()); std::vector aki; if (akid && akid->keyid) { aki.assign(akid->keyid->data, akid->keyid->data + akid->keyid->length); AUTHORITY_KEYID_free(akid); } return aki; } std::unique_ptr CertificateUtil::loadCertFromDER(const std::vector& derData) { const unsigned char* p = derData.data(); X509* cert = d2i_X509(nullptr, &p, derData.size()); if (!cert) { throw OpenSSLError("Failed to parse DER certificate"); } return std::unique_ptr(cert); } std::vector> CertificateUtil::loadCertificateChain(const std::string& filename) { std::vector> chain; if (std::filesystem::path(filename).extension() == ".der") { FILE* file = fopen(filename.c_str(), "rb"); if (!file) { throw std::runtime_error("No certificates found in file: " + filename); } X509* cert = d2i_X509_fp(file, nullptr); chain.push_back(std::unique_ptr(cert)); fclose(file); } else { BIO* bio = BIO_new_file(filename.c_str(), "r"); if (!bio) { throw OpenSSLError("Failed to open certificate file: " + filename); } X509* cert = nullptr; while ((cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr)) != nullptr) { chain.push_back(std::unique_ptr(cert)); } BIO_free(bio); } if (chain.empty()) { throw std::runtime_error("No certificates found in file: " + filename); } return chain; } static std::vector certToDER(X509* cert) { BIO* bio = BIO_new(BIO_s_mem()); if (!bio) { throw OpenSSLError("Failed to create BIO for certificate conversion"); } i2d_X509_bio(bio, cert); BUF_MEM* bufferPtr; BIO_get_mem_ptr(bio, &bufferPtr); std::vector der; der.assign(bufferPtr->data, bufferPtr->data + bufferPtr->length); BIO_free(bio); return der; } bool CertificateUtil::verifyCertificateChainDynamic(X509* cert, const std::vector& certPool, X509* rootCA, bool verbose) { LOG_INFO("Verifying certificate chain..."); LOG_INFO("Target certificate: " + getSubjectName(cert)); // certificate store for trusted roots only std::unique_ptr store(X509_STORE_new()); if (!store) { throw OpenSSLError("Failed to create X509_STORE"); } // Only add the explicitly provided root CA to the trust store if (rootCA) { if (verbose) { LOG_INFO("Adding trusted root CA: " + getSubjectName(rootCA)); } if (X509_STORE_add_cert(store.get(), rootCA) != 1) { throw OpenSSLError("Failed to add root CA to store"); } } else { // If no root CA provided, find self-signed certificates in the pool LOG_WARNING("No explicit root CA provided - searching for self-signed " "certificates"); for (auto candidate : certPool) { if (X509_check_issued(candidate, candidate) == X509_V_OK) { if (verbose) { LOG_INFO("Adding self-signed certificate as trusted root: " + getSubjectName(candidate)); } if (X509_STORE_add_cert(store.get(), candidate) != 1) { unsigned long err = ERR_peek_last_error(); // Ignore duplicate certificate errors if (ERR_GET_REASON(err) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { throw OpenSSLError("Failed to add root CA to store"); } ERR_clear_error(); } } } } // Build untrusted chain from cert pool (excluding the root if it's in // there) IMPORTANT: custom deleter that only frees the stack, not the // certificates auto stack_only_deleter = [](STACK_OF(X509) * stack) { if (stack) { sk_X509_free(stack); // This only frees the stack structure, not the contents } }; std::unique_ptr untrusted_chain(sk_X509_new_null(), stack_only_deleter); if (!untrusted_chain) { throw OpenSSLError("Failed to create untrusted chain"); } // Add all certificates from the pool as untrusted intermediates for (auto poolCert : certPool) { // Skip if this is the target certificate if (X509_cmp(poolCert, cert) == 0) { continue; } // Skip if this is the root CA (already in trust store) if (rootCA && X509_cmp(poolCert, rootCA) == 0) { continue; } // Add to untrusted chain if (sk_X509_push(untrusted_chain.get(), poolCert) == 0) { throw OpenSSLError("Failed to add certificate to untrusted chain"); } } if (verbose) { LOG_INFO("Untrusted chain contains " + std::to_string(sk_X509_num(untrusted_chain.get())) + " certificates"); } std::unique_ptr ctx(X509_STORE_CTX_new()); if (!ctx) { throw OpenSSLError("Failed to create X509_STORE_CTX"); } if (X509_STORE_CTX_init(ctx.get(), store.get(), cert, untrusted_chain.get()) != 1) { throw OpenSSLError("Failed to initialize X509_STORE_CTX"); } unsigned long flags = X509_V_FLAG_CHECK_SS_SIGNATURE; X509_STORE_CTX_set_flags(ctx.get(), flags); X509_STORE_CTX_set_verify_cb(ctx.get(), [](int ok, X509_STORE_CTX* ctx) -> int { if (!ok) { int error = X509_STORE_CTX_get_error(ctx); // SGP.22 EUM certificates use name constraints in a specific way if (error == X509_V_ERR_PERMITTED_VIOLATION || error == X509_V_ERR_EXCLUDED_VIOLATION || error == X509_V_ERR_SUBTREE_MINMAX) { // Get the certificate that triggered this X509* cert = X509_STORE_CTX_get_current_cert(ctx); if (cert) { char subject_buf[256]; X509_NAME_oneline(X509_get_subject_name(cert), subject_buf, sizeof(subject_buf)); LOG_INFO("Processing certificate with name constraint abuse: " + std::string(subject_buf)); } return 1; } } return ok; // Use default behavior for other errors }); int result = X509_verify_cert(ctx.get()); if (result != 1) { int error = X509_STORE_CTX_get_error(ctx.get()); int depth = X509_STORE_CTX_get_error_depth(ctx.get()); X509* errorCert = X509_STORE_CTX_get_current_cert(ctx.get()); LOG_ERROR("Certificate verification failed:"); LOG_ERROR(" Error: " + std::string(X509_verify_cert_error_string(error))); LOG_ERROR(" Depth: " + std::to_string(depth)); if (errorCert) { LOG_ERROR(" Cert: " + getSubjectName(errorCert)); } return false; } LOG_INFO("Certificate verification successful"); if (verbose) { STACK_OF(X509)* verified_chain = X509_STORE_CTX_get1_chain(ctx.get()); if (verified_chain) { LOG_INFO("Verified certificate chain:"); for (int i = 0; i < sk_X509_num(verified_chain); i++) { X509* chainCert = sk_X509_value(verified_chain, i); LOG_INFO(" " + std::to_string(i + 1) + ". " + getSubjectName(chainCert)); } sk_X509_pop_free(verified_chain, X509_free); } } return true; } static std::vector find_cert_files(const std::filesystem::path& root_path, const std::vector& name_filters = {}) { std::vector result; if (!std::filesystem::exists(root_path)) { std::cerr << "Path does not exist: " << root_path << std::endl; return result; } auto file_matches = [&name_filters](const std::filesystem::path& path) { std::string ext = path.extension().string(); if (!(ext == ".pem" || ext == ".der" || ext == ".crt")) { return false; } // If no filters specified, accept all matching extensions if (name_filters.empty()) { return true; } // Check if filename contains any of the filters std::string filename = path.filename().string(); return std::any_of(name_filters.begin(), name_filters.end(), [&filename](const std::string& filter) { return filename.find(filter) != std::string::npos; }); }; if (std::filesystem::is_regular_file(root_path)) { if (file_matches(root_path)) { result.push_back(root_path); } } else if (std::filesystem::is_directory(root_path)) { for (const auto& entry : std::filesystem::recursive_directory_iterator(root_path)) { if (entry.is_regular_file() && file_matches(entry.path())) { result.push_back(entry.path()); } } } return result; } std::vector> CertificateUtil::loadCertificatesFromDirectory(const std::string& directory, const std::vector& name_filters) { std::vector> certificates; auto files_in_dir = find_cert_files(directory, name_filters); for (const auto& entry : files_in_dir) { std::string ext = entry.extension().string(); auto fpath = entry.string(); try { if (ext == ".pem" || ext == ".crt") { auto fileCerts = loadCertificateChain(entry); for (auto& cert : fileCerts) { certificates.push_back(std::move(cert)); } } else if (ext == ".der") { FILE* file = fopen(fpath.c_str(), "rb"); if (!file) { LOG_WARNING("Failed to open certificate file: " + fpath); continue; } fseek(file, 0, SEEK_END); long file_size = ftell(file); rewind(file); std::vector data(file_size); if (fread(data.data(), 1, file_size, file) != static_cast(file_size)) { fclose(file); LOG_WARNING("Failed to read certificate file: " + fpath); continue; } fclose(file); const unsigned char* p = data.data(); X509* cert = d2i_X509(nullptr, &p, file_size); if (cert) { certificates.push_back(std::unique_ptr(cert)); LOG_INFO("Loaded certificate: " + getSubjectName(cert) + " from " + fpath); } } } catch (const std::exception& e) { // already logged in called functions } } return certificates; } static void xx_loadCertificate(const std::string& certPath, const std::string& typeName, std::unique_ptr& certStorage) { try { auto certs = CertificateUtil::loadCertificateChain(certPath); if (certs.empty()) { throw std::runtime_error("No " + typeName + " certificate found in file: " + certPath); } certStorage = std::move(certs[0]); LOG_DEBUG("Loaded " + typeName + " certificate: " + CertificateUtil::getSubjectName(certStorage.get())); } catch (const std::exception& e) { LOG_ERROR("Failed to load " + typeName + " certificate: " + std::string(e.what())); throw; } } static void xx_loadPrivateKey(const std::string& keyPath, const std::string& typeName, std::unique_ptr& keyStorage) { try { if (!std::filesystem::exists(keyPath)) { throw std::runtime_error(typeName + " private key file not found: " + keyPath); } auto keyBio = readFileToBIO(keyPath); keyStorage.reset(PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr)); if (!keyStorage) { throw OpenSSLError("Failed to read " + typeName + " private key"); } LOG_DEBUG("Loaded " + typeName + " private key from: " + keyPath); } catch (const std::exception& e) { LOG_ERROR("Failed to load " + typeName + " private key: " + std::string(e.what())); throw; } } static bool xx_loadPublicKey(const std::string& pubKeyPath, const std::string& typeName, std::vector& publicKeyStorage) { try { if (!std::filesystem::exists(pubKeyPath)) { LOG_INFO(typeName + " public key file not found: " + pubKeyPath + " (will generate from private key)"); return false; } auto keyBio = readFileToBIO(pubKeyPath); std::unique_ptr pubKey(PEM_read_bio_PUBKEY(keyBio.get(), nullptr, nullptr, nullptr)); if (!pubKey) { LOG_WARNING("Failed to read " + typeName + " public key from file (will generate from private key)"); return false; } publicKeyStorage = extractPublicKeyData(pubKey.get()); LOG_INFO("Loaded " + typeName + " public key from file (" + std::to_string(publicKeyStorage.size()) + " bytes)"); return true; } catch (const std::exception& e) { LOG_WARNING("Failed to load " + typeName + " public key from file: " + std::string(e.what()) + " (will generate from private key)"); return false; } } static void xx_generatePublicKeyFromPrivate(const std::unique_ptr& privateKey, const std::string& typeName, std::vector& publicKeyStorage) { if (!privateKey) { throw std::runtime_error("No " + typeName + " private key available for public key generation"); } publicKeyStorage = extractPublicKeyData(privateKey.get()); LOG_DEBUG("Generated " + typeName + " public key from private key (" + std::to_string(publicKeyStorage.size()) + " bytes)"); } static std::vector parse_permitted_eins_from_cert(X509* cert) { std::vector permitted_iins; bool is_old_variant = false; int pos = X509_get_ext_by_NID(cert, NID_certificate_policies, -1); if (pos >= 0) { X509_EXTENSION* ext = X509_get_ext(cert, pos); CERTIFICATEPOLICIES* policies = (CERTIFICATEPOLICIES*)X509V3_EXT_d2i(ext); if (policies) { for (int i = 0; i < sk_POLICYINFO_num(policies); i++) { POLICYINFO* policy = sk_POLICYINFO_value(policies, i); char oid_str[128]; OBJ_obj2txt(oid_str, sizeof(oid_str), policy->policyid, 1); if (std::string(oid_str) == "2.23.146.1.2.1.2") { is_old_variant = true; break; } } CERTIFICATEPOLICIES_free(policies); } } if (is_old_variant) { // Parse from nameConstraints pos = X509_get_ext_by_NID(cert, NID_name_constraints, -1); if (pos >= 0) { X509_EXTENSION* ext = X509_get_ext(cert, pos); NAME_CONSTRAINTS* nc = (NAME_CONSTRAINTS*)X509V3_EXT_d2i(ext); if (nc && nc->permittedSubtrees) { for (int i = 0; i < sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); i++) { GENERAL_SUBTREE* subtree = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, i); if (subtree->base->type == GEN_DIRNAME) { X509_NAME* name = subtree->base->d.directoryName; int idx = X509_NAME_get_index_by_NID(name, NID_serialNumber, -1); if (idx >= 0) { X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, idx); ASN1_STRING* asn1_str = X509_NAME_ENTRY_get_data(entry); std::string iin(reinterpret_cast(ASN1_STRING_get0_data(asn1_str)), ASN1_STRING_length(asn1_str)); permitted_iins.push_back(iin); } } } } if (nc) NAME_CONSTRAINTS_free(nc); } } else { // Parse from GSMA permittedEins extension ASN1_OBJECT* obj = OBJ_txt2obj("2.23.146.1.2.2.0", 1); pos = X509_get_ext_by_OBJ(cert, obj, -1); ASN1_OBJECT_free(obj); if (pos >= 0) { X509_EXTENSION* ext = X509_get_ext(cert, pos); ASN1_OCTET_STRING* ext_data = X509_EXTENSION_get_data(ext); const unsigned char* p = ext_data->data; long remaining = ext_data->length; // SEQUENCE OF PrintableString int tag, xclass; long xlen; // SEQUENCE tag ASN1_get_object(&p, &xlen, &tag, &xclass, remaining); if (tag == V_ASN1_SEQUENCE) { remaining = xlen; while (remaining > 0) { const unsigned char* elem_start = p; ASN1_get_object(&p, &xlen, &tag, &xclass, remaining); if (tag == V_ASN1_PRINTABLESTRING) { std::string iin(reinterpret_cast(p), xlen); permitted_iins.push_back(iin); } p += xlen; remaining -= (p - elem_start); } } } } return permitted_iins; } std::string CertificateUtil::getPermittedEINs(const std::vector& eumCertData) { try { auto cert = loadCertFromDER(eumCertData); auto eins = parse_permitted_eins_from_cert(cert.get()); // Join EINs with comma std::string result; for (size_t i = 0; i < eins.size(); i++) { if (i > 0) result += ","; result += eins[i]; } return result; } catch (const std::exception& e) { LOG_ERROR("getPermittedEINs failed: " + std::string(e.what())); return ""; } } bool CertificateUtil::validateEIDRange(const std::string& eid, const std::vector& eumCertData) { try { auto eumCert = loadCertFromDER(eumCertData); std::vector permittedEins = parse_permitted_eins_from_cert(eumCert.get()); if (permittedEins.empty()) { LOG_WARNING("No permitted EINs found in EUM certificate"); return false; } // Check if EID starts with any permitted EIN std::string eidNormalized = eid; std::transform(eidNormalized.begin(), eidNormalized.end(), eidNormalized.begin(), ::toupper); for (const auto& ein : permittedEins) { if (eidNormalized.find(ein) == 0) { LOG_INFO("EID " + eidNormalized + " matches permitted EIN " + ein); return true; } } LOG_ERROR("EID " + eidNormalized + " is not in any permitted EIN list"); return false; } catch (const std::exception& e) { LOG_ERROR("validateEIDRange failed: " + std::string(e.what())); return false; } } bool CertificateUtil::hasRSPRole(const std::vector& certData, const std::string& roleOid) { try { auto cert = loadCertFromDER(certData); int pos = X509_get_ext_by_NID(cert.get(), NID_certificate_policies, -1); if (pos < 0) return false; X509_EXTENSION* ext = X509_get_ext(cert.get(), pos); CERTIFICATEPOLICIES* policies = (CERTIFICATEPOLICIES*)X509V3_EXT_d2i(ext); if (!policies) return false; for (int i = 0; i < sk_POLICYINFO_num(policies); i++) { POLICYINFO* policy = sk_POLICYINFO_value(policies, i); char oid_str[128]; OBJ_obj2txt(oid_str, sizeof(oid_str), policy->policyid, 1); if (std::string(oid_str) == roleOid) { CERTIFICATEPOLICIES_free(policies); return true; } } CERTIFICATEPOLICIES_free(policies); return false; } catch (const std::exception& e) { LOG_ERROR("hasRSPRole failed: " + std::string(e.what())); return false; } } std::string CertificateUtil::getCurveOID(const std::vector& certData) { try { auto cert = loadCertFromDER(certData); EVP_PKEY* pkey = X509_get0_pubkey(cert.get()); if (!pkey) { LOG_ERROR("Failed to get public key from certificate"); return ""; } if (EVP_PKEY_base_id(pkey) != EVP_PKEY_EC) { LOG_ERROR("Certificate does not contain EC key"); return ""; } char curve_name[256] = { 0 }; size_t curve_name_len = sizeof(curve_name); if (EVP_PKEY_get_utf8_string_param(pkey, "group", curve_name, sizeof(curve_name), &curve_name_len) != 1) { LOG_ERROR("Failed to get curve name from EC key"); return ""; } std::string curve_str(curve_name); if (curve_str == "prime256v1") { return "prime256v1"; } else if (curve_str == "secp384r1") { return "secp384r1"; } else if (curve_str == "brainpoolP256r1") { return "brainpoolP256r1"; } else if (curve_str == "brainpoolP384r1") { return "brainpoolP384r1"; } return "unknown"; } catch (const std::exception& e) { LOG_ERROR("getCurveOID failed: " + std::string(e.what())); return ""; } } // ECDH compatible (same curve) bool CertificateUtil::verifyECDHCompatible(const std::vector& pubKey1, const std::vector& pubKey2) { try { // Both should be uncompressed EC points starting with 0x04 if (pubKey1.empty() || pubKey2.empty() || pubKey1[0] != 0x04 || pubKey2[0] != 0x04) { LOG_ERROR("Invalid EC public key format"); return false; } // For P-256, size should be 65 bytes (1 + 32 + 32) if (pubKey1.size() == 65 && pubKey2.size() == 65) { LOG_INFO("Both keys are P-256 format, ECDH compatible"); return true; } // For P-384, size should be 97 bytes (1 + 48 + 48) if (pubKey1.size() == 97 && pubKey2.size() == 97) { LOG_INFO("Both keys are P-384 format, ECDH compatible"); return true; } LOG_ERROR("Key sizes don't match or unsupported curve"); return false; } catch (const std::exception& e) { LOG_ERROR("verifyECDHCompatible failed: " + std::string(e.what())); return false; } } static std::vector convertBSI_TR03111_to_DER(const std::vector& bsi_signature) { if (bsi_signature.size() != 64) { std::cerr << "Invalid BSI TR-03111 signature size: " << bsi_signature.size() << std::endl; return {}; } BIGNUM* r = BN_new(); BIGNUM* s = BN_new(); if (!r || !s) { if (r) BN_free(r); if (s) BN_free(s); return {}; } BN_bin2bn(bsi_signature.data(), 32, r); BN_bin2bn(bsi_signature.data() + 32, 32, s); ECDSA_SIG* ecdsa_sig = ECDSA_SIG_new(); if (!ecdsa_sig) { BN_free(r); BN_free(s); return {}; } ECDSA_SIG_set0(ecdsa_sig, r, s); int der_len = i2d_ECDSA_SIG(ecdsa_sig, nullptr); if (der_len <= 0) { ECDSA_SIG_free(ecdsa_sig); return {}; } std::vector der_signature(der_len); unsigned char* der_ptr = der_signature.data(); i2d_ECDSA_SIG(ecdsa_sig, &der_ptr); ECDSA_SIG_free(ecdsa_sig); return der_signature; } static std::vector convertDER_to_BSI_TR03111(const std::vector& der_signature) { if (der_signature.empty()) { std::cerr << "Empty DER signature" << std::endl; return {}; } const unsigned char* der_ptr = der_signature.data(); ECDSA_SIG* ecdsa_sig = d2i_ECDSA_SIG(nullptr, &der_ptr, der_signature.size()); if (!ecdsa_sig) { std::cerr << "Failed to parse DER signature" << std::endl; return {}; } const BIGNUM *r, *s; ECDSA_SIG_get0(ecdsa_sig, &r, &s); if (!r || !s) { ECDSA_SIG_free(ecdsa_sig); return {}; } std::vector bsi_signature(64, 0); int r_len = BN_num_bytes(r); int s_len = BN_num_bytes(s); if (r_len > 32 || s_len > 32) { std::cerr << "BIGNUM values too large for BSI TR-03111 format" << std::endl; ECDSA_SIG_free(ecdsa_sig); return {}; } // Convert r to bytes (with leading zeros padding) int r_offset = 32 - r_len; BN_bn2bin(r, bsi_signature.data() + r_offset); // Convert s to bytes (with leading zeros padding) int s_offset = 64 - s_len; BN_bn2bin(s, bsi_signature.data() + s_offset); ECDSA_SIG_free(ecdsa_sig); return bsi_signature; } static bool verify_TR031111(const std::vector& message, const std::vector& bsi_signature, EVP_PKEY* publicKey) { std::vector der_signature = convertBSI_TR03111_to_DER(bsi_signature); if (der_signature.empty()) { std::cerr << "Failed to convert signature to DER format" << std::endl; return false; } std::unique_ptr mdctx(EVP_MD_CTX_new()); if (!mdctx.get()) { std::cerr << "Failed to create MD context" << std::endl; return false; } if (EVP_DigestVerifyInit(mdctx.get(), nullptr, EVP_sha256(), nullptr, publicKey) != 1) { std::cerr << "Failed to initialize digest verify" << std::endl; return false; } if (EVP_DigestVerifyUpdate(mdctx.get(), message.data(), message.size()) != 1) { std::cerr << "Failed to update digest verify" << std::endl; return false; } int result = EVP_DigestVerifyFinal(mdctx.get(), der_signature.data(), der_signature.size()); return result == 1; } // HttpClient implementations HttpClient::HttpClient() { // should be called once per application curl_global_init(CURL_GLOBAL_DEFAULT); } HttpClient::~HttpClient() { curl_global_cleanup(); } // Private helper functions for HttpClient size_t HttpClient::writeCallback(void* contents, size_t size, size_t nmemb, void* userp) { size_t realSize = size * nmemb; auto* response = static_cast(userp); response->append(static_cast(contents), realSize); return realSize; } size_t HttpClient::headerCallback(void* contents, size_t size, size_t nmemb, void* userp) { size_t realSize = size * nmemb; auto* headers = static_cast(userp); headers->append(static_cast(contents), realSize); return realSize; } std::string HttpClient::get_cn_name(X509_NAME* const name) { int idx = -1; unsigned char* utf8 = NULL; X509_NAME_ENTRY* entry; ASN1_STRING* data; if (!name) return {}; idx = X509_NAME_get_index_by_NID(name, NID_commonName, -1); if (idx < 0) return {}; entry = X509_NAME_get_entry(name, idx); if (!entry) return {}; data = X509_NAME_ENTRY_get_data(entry); if (!data) return {}; if (!ASN1_STRING_to_UTF8(&utf8, data) || !utf8) return {}; std::string retstr((char*)utf8); OPENSSL_free(utf8); return retstr; } CURLcode HttpClient::sslCtxFunction(CURL* curl, SSL_CTX* sslCtx, void* arg) { SslCtxData* ctxData = static_cast(arg); SSL_CTX* ctx = (SSL_CTX*)sslCtx; X509_STORE* store = SSL_CTX_get_cert_store(ctx); for (auto cert : *ctxData->certPool) { LOG_DEBUG("ADDED Issuer: " + get_cn_name(X509_get_issuer_name(cert)) + " Subject: " + get_cn_name(X509_get_subject_name(cert)) + " SKI: " + HexUtil::bytesToHex(CertificateUtil::getSubjectKeyIdentifier(cert)) + " AKI: " + HexUtil::bytesToHex(CertificateUtil::getAuthorityKeyIdentifier(cert))); auto result = X509_STORE_add_cert(store, cert); if (result != 1) { printf("Warning: Could not add certificate\n"); unsigned long err = ERR_get_error(); if (ERR_GET_REASON(err) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { ERR_print_errors_fp(stderr); } } } SSL_CTX_set_verify(sslCtx, SSL_VERIFY_PEER, nullptr); return CURLE_OK; } HttpClient::ResponseData HttpClient::postJson(const std::string& url, unsigned int port, const std::string& jsonData, X509_STORE* store, std::vector& certPool, const PostConfig& config) { ResponseData response; CURL* curl = curl_easy_init(); if (!curl) { throw std::runtime_error("Failed to initialize CURL"); } struct curl_slist* headers = nullptr; std::string contentTypeHeader = "Content-Type: " + config.contentType; if (config.useMutualTLS) { if (config.contentType == "application/json") { contentTypeHeader += ";charset=UTF-8"; } headers = curl_slist_append(headers, contentTypeHeader.c_str()); headers = curl_slist_append(headers, "Accept: application/json"); if (config.includeAdminProtocolHeader) { headers = curl_slist_append(headers, "X-Admin-Protocol: gsma/rsp/v2.5.0"); } } else { headers = curl_slist_append(headers, contentTypeHeader.c_str()); // For ASN.1, accept the same content type std::string acceptHeader = "Accept: " + (config.contentType == "application/x-gsma-rsp-asn1" ? config.contentType : "application/json"); headers = curl_slist_append(headers, acceptHeader.c_str()); // Always add X-Admin-Protocol for ES9+ regardless of content type headers = curl_slist_append(headers, "X-Admin-Protocol: gsma/rsp/v2.5.0"); } SslCtxData ctxData = { .store = store, .certPool = &certPool, .verifyResult = false, .errorMessage = "" }; try { // Basic CURL setup curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_PORT, port); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, jsonData.size()); // Important for binary data curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response.body); curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, headerCallback); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &response.headers); // Enable SSL verification curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L); // Set reasonable timeouts curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); if (config.useMutualTLS) { if (!config.clientCertPath.empty()) { curl_easy_setopt(curl, CURLOPT_SSLCERT, config.clientCertPath.c_str()); std::string certType = "PEM"; if (config.clientCertPath.find(".der") != std::string::npos || config.clientCertPath.find(".DER") != std::string::npos) { certType = "DER"; } curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, certType.c_str()); LOG_DEBUG("Set client certificate: " + config.clientCertPath + " (type: " + certType + ")"); } if (!config.clientKeyPath.empty()) { curl_easy_setopt(curl, CURLOPT_SSLKEY, config.clientKeyPath.c_str()); curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM"); LOG_DEBUG("Set client private key: " + config.clientKeyPath); } } // Use our custom SSL context function for server cert verification curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, sslCtxFunction); curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA, &ctxData); curl_easy_setopt(curl, CURLOPT_VERBOSE, config.verboseOutput ? 1L : 0L); CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { throw std::runtime_error(std::string("CURL request failed: ") + curl_easy_strerror(res)); } curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response.statusCode); curl_slist_free_all(headers); curl_easy_cleanup(curl); return response; } catch (...) { curl_slist_free_all(headers); curl_easy_cleanup(curl); throw; } } // Hashed Confirmation Code = SHA256(SHA256(Confirmation Code) | TransactionID) static std::vector computeConfirmationCodeHash(const std::string& confirmationCode, const std::vector& transactionId) { // Convert confirmation code from hex string to bytes (like EID handling) std::vector ccBytes; for (size_t i = 0; i < confirmationCode.length(); i += 2) { if (i + 1 < confirmationCode.length()) { std::string byteString = confirmationCode.substr(i, 2); uint8_t byte = static_cast(std::stoi(byteString, nullptr, 16)); ccBytes.push_back(byte); } } unsigned char firstHash[SHA256_DIGEST_LENGTH]; SHA256(ccBytes.data(), ccBytes.size(), firstHash); std::vector concatenated; concatenated.insert(concatenated.end(), firstHash, firstHash + SHA256_DIGEST_LENGTH); concatenated.insert(concatenated.end(), transactionId.begin(), transactionId.end()); unsigned char finalHash[SHA256_DIGEST_LENGTH]; SHA256(concatenated.data(), concatenated.size(), finalHash); return std::vector(finalHash, finalHash + SHA256_DIGEST_LENGTH); } // RSPClient implementations RSPClient::RSPClient(const std::string& serverUrl, const unsigned int serverPort, const std::vector& certPath, const std::vector& name_filters) : m_serverUrl(serverUrl) { // OpenSSL 3.0+ initializes automatically for (auto cpath : certPath) { bool isDirectory = std::filesystem::is_directory(cpath); try { if (isDirectory) { LOG_DEBUG("Loading certificates from directory: " + cpath); auto certs = CertificateUtil::loadCertificatesFromDirectory(cpath, name_filters); LOG_DEBUG("Loaded " + std::to_string(certs.size()) + " certificates"); // Separate root CAs and intermediates for (auto& cert : certs) { // Identify root certificates (self-signed) if (X509_check_issued(cert.get(), cert.get()) == X509_V_OK) { LOG_DEBUG("Found root CA: " + CertificateUtil::getSubjectName(cert.get())); // Store the first root CA as our primary root if (!m_rootCA) { m_rootCA = std::move(cert); } else { // Store additional roots for chain verification m_certPool.push_back(std::move(cert)); } } else { // Store intermediate certificates m_certPool.push_back(std::move(cert)); } } if (!m_rootCA) { LOG_WARNING("No root CA found in directory"); } } else { // load certificate chain from single file LOG_INFO("Loading certificates from file: " + cpath); auto caChain = CertificateUtil::loadCertificateChain(cpath); if (!caChain.empty()) { m_rootCA = std::move(caChain[0]); for (size_t i = 1; i < caChain.size(); ++i) { m_intermediateCA.push_back(std::move(caChain[i])); } LOG_INFO("Loaded root CA: " + CertificateUtil::getSubjectName(m_rootCA.get())); for (const auto& cert : m_intermediateCA) { LOG_INFO("Loaded intermediate CA: " + CertificateUtil::getSubjectName(cert.get())); // add intermediates to certificate pool for compatibility m_certPool.push_back(std::unique_ptr(X509_dup(cert.get()))); } } } } catch (const std::exception& e) { LOG_ERROR("Failed to load certificates: " + std::string(e.what())); throw; } } } RSPClient::~RSPClient() { // OpenSSL 3.0+ handles cleanup automatically } void RSPClient::setCACertPath(const std::string& path) { m_caCertPath = path; } void RSPClient::loadEUICCCertificate(const std::string& euiccCertPath) { loadCertificate(euiccCertPath, "eUICC", m_euiccCert); try { m_EID = CertificateUtil::getEID(m_euiccCert.get()); LOG_DEBUG("Using EID from certificate: " + m_EID); auto ski = CertificateUtil::getSubjectKeyIdentifier(m_euiccCert.get()); m_euiccSKI = ski; LOG_DEBUG("Using eUICC SKI as PKID: " + HexUtil::bytesToHex(m_euiccSKI)); } catch (const std::exception& e) { LOG_ERROR("Failed to extract EUICC-specific data: " + std::string(e.what())); throw; } } void RSPClient::loadEUICCKeyPair(const std::string& euiccPrivateKeyPath, const std::string& euiccPublicKeyPath) { loadKeyPair(euiccPrivateKeyPath, euiccPublicKeyPath, "eUICC", m_euiccPrivateKey, m_euiccPublicKeyData); } void RSPClient::loadEUMCertificate(const std::string& eumCertPath) { loadCertificate(eumCertPath, "EUM", m_eumCert); } void RSPClient::loadEUMKeyPair(const std::string& eumPrivateKeyPath, const std::string& eumPublicKeyPath) { loadKeyPair(eumPrivateKeyPath, eumPublicKeyPath, "EUM", m_eumPrivateKey, m_eumPublicKeyData); } std::vector RSPClient::generateChallenge() { std::vector challenge(16); if (RAND_bytes(challenge.data(), challenge.size()) != 1) { throw OpenSSLError("Failed to generate random challenge"); } return challenge; } int getCertificateCurveNID(X509* cert) { if (!cert) { throw std::runtime_error("Certificate is null"); } std::unique_ptr pubkey(X509_get_pubkey(cert)); if (!pubkey) { throw OpenSSLError("Failed to extract public key from certificate"); } char curve_name[80]; size_t curve_name_len = sizeof(curve_name); if (EVP_PKEY_get_utf8_string_param(pubkey.get(), OSSL_PKEY_PARAM_GROUP_NAME, curve_name, sizeof(curve_name), &curve_name_len) != 1) { throw OpenSSLError("Failed to get curve name from certificate"); } std::string curve_str(curve_name); int curve_nid; if (curve_str == "prime256v1" || curve_str == "P-256") { curve_nid = NID_X9_62_prime256v1; } else if (curve_str == "brainpoolP256r1") { curve_nid = NID_brainpoolP256r1; } else { throw std::runtime_error("Unsupported curve in certificate: " + curve_str); } return curve_nid; } int RSPClient::getEUICCCurveNID() { if (!m_euiccCert) { throw std::runtime_error("eUICC certificate not loaded"); } int curve_nid = getCertificateCurveNID(m_euiccCert.get()); if (curve_nid == NID_X9_62_prime256v1) { LOG_INFO("eUICC certificate uses P-256 curve"); } else if (curve_nid == NID_brainpoolP256r1) { LOG_INFO("eUICC certificate uses brainpoolP256r1 curve"); } return curve_nid; } void RSPClient::generateEUICCOtpk() { int curve_nid = getEUICCCurveNID(); std::string curve_name = (curve_nid == NID_X9_62_prime256v1) ? "P-256" : "brainpoolP256r1"; std::unique_ptr pctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr), EVP_PKEY_CTX_free); if (!pctx) { throw OpenSSLError("Failed to create EVP_PKEY_CTX"); } if (EVP_PKEY_keygen_init(pctx.get()) <= 0) { throw OpenSSLError("Failed to initialize key generation"); } if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx.get(), curve_nid) <= 0) { throw OpenSSLError("Failed to set " + curve_name + " curve"); } EVP_PKEY* pkey_raw = nullptr; if (EVP_PKEY_keygen(pctx.get(), &pkey_raw) <= 0) { throw OpenSSLError("Failed to generate EC key pair"); } m_euicc_ot_PrivateKey.reset(pkey_raw); size_t pub_len = 0; if (EVP_PKEY_get_octet_string_param(m_euicc_ot_PrivateKey.get(), OSSL_PKEY_PARAM_PUB_KEY, nullptr, 0, &pub_len) != 1) { throw OpenSSLError("Failed to get public key size"); } // Both P-256 and brainpoolP256r1 have 256-bit (32 byte) coordinates // Uncompressed format: 0x04 + 32 bytes X + 32 bytes Y = 65 bytes if (pub_len != 65) { throw OpenSSLError("Unexpected public key size for " + curve_name); } m_euiccOtpk.resize(pub_len); if (EVP_PKEY_get_octet_string_param(m_euicc_ot_PrivateKey.get(), OSSL_PKEY_PARAM_PUB_KEY, m_euiccOtpk.data(), pub_len, &pub_len) != 1) { throw OpenSSLError("Failed to extract public key"); } LOG_INFO("Generated eUICC OtPK (" + curve_name + "): " + HexUtil::bytesToHex(m_euiccOtpk)); if (m_euiccOtpk[0] != 0x04) { throw std::runtime_error("Invalid public key format - expected uncompressed point"); } LOG_DEBUG("Public key format verified - uncompressed EC point"); } std::vector RSPClient::computeECDHSharedSecret(const std::vector& otherPublicKey) { if (!m_euicc_ot_PrivateKey) { throw std::runtime_error("eUICC ephemeral private key not available"); } int curve_nid = getEUICCCurveNID(); const char* curve_param_name = (curve_nid == NID_X9_62_prime256v1) ? "P-256" : "brainpoolP256r1"; std::unique_ptr pctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, nullptr), EVP_PKEY_CTX_free); if (!pctx) { throw OpenSSLError("Failed to create EVP_PKEY_CTX"); } if (EVP_PKEY_fromdata_init(pctx.get()) <= 0) { throw OpenSSLError("Failed to initialize fromdata"); } OSSL_PARAM_BLD* param_bld = OSSL_PARAM_BLD_new(); if (!param_bld) { throw OpenSSLError("Failed to create OSSL_PARAM_BLD"); } if (!OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, curve_param_name, 0)) { OSSL_PARAM_BLD_free(param_bld); throw OpenSSLError("Failed to set curve name"); } if (!OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, otherPublicKey.data(), otherPublicKey.size())) { OSSL_PARAM_BLD_free(param_bld); throw OpenSSLError("Failed to set public key"); } OSSL_PARAM* params = OSSL_PARAM_BLD_to_param(param_bld); if (!params) { OSSL_PARAM_BLD_free(param_bld); throw OpenSSLError("Failed to build params"); } EVP_PKEY* other_pkey_raw = nullptr; if (EVP_PKEY_fromdata(pctx.get(), &other_pkey_raw, EVP_PKEY_PUBLIC_KEY, params) <= 0) { OSSL_PARAM_free(params); OSSL_PARAM_BLD_free(param_bld); throw OpenSSLError("Failed to create public key from data"); } OSSL_PARAM_free(params); OSSL_PARAM_BLD_free(param_bld); std::unique_ptr other_pkey(other_pkey_raw); std::unique_ptr derive_ctx(EVP_PKEY_CTX_new(m_euicc_ot_PrivateKey.get(), nullptr), EVP_PKEY_CTX_free); if (!derive_ctx) { throw OpenSSLError("Failed to create derive context"); } if (EVP_PKEY_derive_init(derive_ctx.get()) <= 0) { throw OpenSSLError("Failed to initialize key derivation"); } if (EVP_PKEY_derive_set_peer(derive_ctx.get(), other_pkey.get()) <= 0) { throw OpenSSLError("Failed to set peer key"); } size_t secret_len = 0; if (EVP_PKEY_derive(derive_ctx.get(), nullptr, &secret_len) <= 0) { throw OpenSSLError("Failed to get shared secret length"); } std::vector shared_secret(secret_len); if (EVP_PKEY_derive(derive_ctx.get(), shared_secret.data(), &secret_len) <= 0) { throw OpenSSLError("ECDH computation failed"); } shared_secret.resize(secret_len); LOG_INFO("Computed ECDH shared secret: " + HexUtil::bytesToHex(shared_secret)); return shared_secret; } std::vector RSPClient::signDataWithEUICC(const std::vector& dataToSign) { if (!m_euiccPrivateKey) { throw std::runtime_error("eUICC private key not available for signing"); } auto rv = signDataWithKey(dataToSign, m_euiccPrivateKey.get()); return convertDER_to_BSI_TR03111(rv); } bool RSPClient::verifyServerSignature(const std::vector& serverSigned1, const std::vector& serverSignature1, const std::vector& derDataServerCert) { auto serverCert = CertificateUtil::loadCertFromDER(derDataServerCert); return verifyServerSignature(serverSigned1, serverSignature1, serverCert.get()); } void RSPClient::setConfirmationCode(const std::string& confirmationCode) { m_confirmationCode = confirmationCode; if (!m_transactionId.empty()) { m_confirmationCodeHash = computeConfirmationCodeHash(m_confirmationCode, m_transactionId); LOG_INFO("Set confirmation code, computed hash: " + HexUtil::bytesToHex(m_confirmationCodeHash)); } else { LOG_ERROR("Cannot compute confirmation code hash - transaction ID not set"); } } void RSPClient::setTransactionId(const std::vector& transactionId) { m_transactionId = transactionId; LOG_INFO("Set transaction ID: " + HexUtil::bytesToHex(transactionId)); // If confirmation code was already set, recalculate hash if (!m_confirmationCode.empty()) { m_confirmationCodeHash = computeConfirmationCodeHash(m_confirmationCode, m_transactionId); LOG_INFO("Recalculated confirmation code hash: " + HexUtil::bytesToHex(m_confirmationCodeHash)); } } std::vector RSPClient::getConfirmationCodeHash() const { return m_confirmationCodeHash; } void RSPClient::setConfirmationCodeHash(const std::vector& hash) { m_confirmationCodeHash = hash; LOG_INFO("Set confirmation code hash directly: " + HexUtil::bytesToHex(hash)); } std::vector RSPClient::getEUMCertificate() { return certToDER(m_eumCert.get()); } std::vector RSPClient::getEUICCCertificate() { return certToDER(m_euiccCert.get()); } std::vector RSPClient::getCICertificate() { // If no eUICC certificate is loaded, return the primary root CA if (!m_euiccCert) { if (m_rootCA) { return certToDER(m_rootCA.get()); } return std::vector(); } int euicc_curve_nid = getEUICCCurveNID(); if (m_rootCA) { try { int root_curve_nid = getCertificateCurveNID(m_rootCA.get()); if (root_curve_nid == euicc_curve_nid) { LOG_DEBUG("Primary root CA matches eUICC curve type"); return certToDER(m_rootCA.get()); } } catch (const std::exception& e) { LOG_WARNING("Failed to get curve type from primary root CA: " + std::string(e.what())); } } // Search for a matching root CA in the certificate pool for (const auto& cert : m_certPool) { // Check if this is a self-signed certificate (root CA) if (X509_check_issued(cert.get(), cert.get()) == X509_V_OK) { try { int cert_curve_nid = getCertificateCurveNID(cert.get()); if (cert_curve_nid == euicc_curve_nid) { LOG_DEBUG("Found matching root CA in certificate pool for curve type"); return certToDER(cert.get()); } } catch (const std::exception& e) { LOG_WARNING("Failed to get curve type from certificate: " + std::string(e.what())); } } } // If no matching root CA found, return the primary root CA as fallback LOG_WARNING("No root CA found matching eUICC curve type, returning primary " "root CA"); if (m_rootCA) { return certToDER(m_rootCA.get()); } return std::vector(); } std::vector RSPClient::getEUICCOtpk() { return m_euiccOtpk; } // Private RSPClient methods void RSPClient::loadCertificate(const std::string& certPath, const std::string& certType, std::unique_ptr& certStorage) { xx_loadCertificate(certPath, certType, certStorage); } void RSPClient::loadKeyPair(const std::string& privateKeyPath, const std::string& publicKeyPath, const std::string& keyType, std::unique_ptr& privateKeyStorage, std::vector& publicKeyStorage) { xx_loadPrivateKey(privateKeyPath, keyType, privateKeyStorage); if (!publicKeyPath.empty() && xx_loadPublicKey(publicKeyPath, keyType, publicKeyStorage)) { return; } xx_generatePublicKeyFromPrivate(privateKeyStorage, keyType, publicKeyStorage); } std::vector RSPClient::signDataWithKey(const std::vector& dataToSign, EVP_PKEY* pkey) { if (dataToSign.empty()) { throw std::runtime_error("Data to sign is empty"); } std::unique_ptr mdctx(EVP_MD_CTX_new()); if (!mdctx) { throw OpenSSLError("Failed to create signature context"); } if (EVP_DigestSignInit(mdctx.get(), nullptr, EVP_sha256(), nullptr, pkey) != 1) { throw OpenSSLError("Failed to initialize signature operation"); } if (EVP_DigestSignUpdate(mdctx.get(), dataToSign.data(), dataToSign.size()) != 1) { throw OpenSSLError("Failed to update signature"); } size_t sigLen; if (EVP_DigestSignFinal(mdctx.get(), nullptr, &sigLen) != 1) { throw OpenSSLError("Failed to determine signature length"); } if (sigLen == 0) { throw std::runtime_error("signature length is zero"); } std::vector signature(sigLen); if (EVP_DigestSignFinal(mdctx.get(), signature.data(), &sigLen) != 1) { throw OpenSSLError("Failed to create signature"); } signature.resize(sigLen); LOG_DEBUG("Created signature (" + std::to_string(sigLen) + " bytes) over " + std::to_string(dataToSign.size()) + " bytes of data"); return signature; } bool RSPClient::verifyServerSignature(const std::vector& signedData, const std::vector& signature, X509* scertdata) { try { // Handle prefix in serverSignature1 based on SGP.22 spec const unsigned char* signatureData = signature.data(); size_t signatureLen = signature.size(); if (signatureLen >= 3 && signatureData[0] == 0x5f && signatureData[1] == 0x37 && signatureData[2] == 0x40) { LOG_DEBUG("Detected 5F3740 prefix in serverSignature1, skipping for " "verification"); signatureData += 3; signatureLen -= 3; } std::unique_ptr pubkey(X509_get_pubkey(scertdata)); if (!pubkey) { throw OpenSSLError("Failed to extract public key from certificate"); } auto ski = CertificateUtil::getSubjectKeyIdentifier(scertdata); LOG_DEBUG(" using cert with this SKI to verify sig: " + HexUtil::bytesToHex(ski)); auto verifyResult = verify_TR031111(signedData, std::vector(signatureData, signatureData + signatureLen), pubkey.get()); if (verifyResult) { LOG_INFO("Signature verification successful!"); return true; } else { LOG_ERROR("Signature verification failed - invalid signature"); return false; } } catch (const std::exception& e) { LOG_ERROR("Error verifying signature: " + std::string(e.what())); return false; } } bool RSPClient::verifyServerSignature(const std::vector& serverSigned1, const std::vector& serverSignature1) { return verifyServerSignature(serverSigned1, serverSignature1, m_serverCert.get()); } void RSPClient::configureHttpClient(bool useCustomTlsCert, const std::string& customTlsCertPath) { m_useCustomTlsCert = useCustomTlsCert; m_customTlsCertPath = customTlsCertPath; } // Set authentication parameters for mutual TLS auth void RSPClient::setAuthParams(bool useMutualTLS, const std::string& clientCertPath, const std::string& clientKeyPath) { m_useMutualTLS = useMutualTLS; m_clientCertPath = clientCertPath; m_clientKeyPath = clientKeyPath; LOG_DEBUG("Auth params set: mutualTLS=" + std::to_string(useMutualTLS) + ", cert=" + clientCertPath + ", key=" + clientKeyPath); } // Send HTTPS POST without client authentication (for ES9+) std::string RSPClient::sendHttpsPost(const std::string& endpoint, const std::string& body, int& httpStatusCode, unsigned int portOverride) { return sendHttpsPostUnified(endpoint, body, httpStatusCode, portOverride); } // Send HTTPS POST with pre-configured client authentication (for ES2+) std::string RSPClient::sendHttpsPostWithAuth(const std::string& endpoint, const std::string& body, int& httpStatusCode, unsigned int portOverride) { if (!m_useMutualTLS) { LOG_WARNING("sendHttpsPostWithAuth called but mutual TLS not configured"); } return sendHttpsPostUnified(endpoint, body, httpStatusCode, portOverride, m_useMutualTLS, m_clientCertPath, m_clientKeyPath); } std::string RSPClient::sendHttpsPostWithContentType(const std::string& endpoint, const std::string& body, const std::string& contentType, int& httpStatusCode, unsigned int portOverride) { LOG_DEBUG("sendHttpsPostWithContentType: endpoint=" + endpoint + ", contentType=" + contentType + ", bodySize=" + std::to_string(body.size()) + ", port=" + std::to_string(portOverride)); return sendHttpsPostUnified(endpoint, body, httpStatusCode, portOverride, false, "", "", contentType); } std::string RSPClient::sendHttpsPostUnified(const std::string& endpoint, const std::string& body, int& httpStatusCode, unsigned int portOverride, bool useMutualTLS, const std::string& clientCertPath, const std::string& clientKeyPath, const std::string& contentType) { if (!m_httpClient) { m_httpClient = std::make_unique(); } // full URL without port (port set via CURLOPT_PORT) std::string url = "https://" + m_serverUrl + endpoint; // Build certificate pool for server verification std::vector rawCertPool; for (const auto& cert : m_certPool) { rawCertPool.push_back(cert.get()); } // Add custom TLS server certificate if configured globally if (m_useCustomTlsCert && !m_customTlsCertPath.empty()) { try { auto tlsCerts = CertificateUtil::loadCertificateChain(m_customTlsCertPath); for (auto& cert : tlsCerts) { rawCertPool.push_back(cert.get()); } LOG_DEBUG("Added custom TLS server certificate from: " + m_customTlsCertPath); } catch (const std::exception& e) { LOG_WARNING("Failed to load custom TLS server certificate: " + std::string(e.what())); } } HttpClient::PostConfig config; config.useMutualTLS = useMutualTLS; config.clientCertPath = clientCertPath; config.clientKeyPath = clientKeyPath; config.includeAdminProtocolHeader = useMutualTLS; // ES2+ requires this header config.verboseOutput = false; config.contentType = contentType; LOG_DEBUG("Sending " + std::string(useMutualTLS ? "ES2+ request with mutual TLS" : "HTTPS request") + " to: " + endpoint + " (port: " + std::to_string(portOverride) + ")"); HttpClient::ResponseData response = m_httpClient->postJson(url, portOverride, body, nullptr, rawCertPool, config); httpStatusCode = response.statusCode; LOG_DEBUG("HTTP " + std::to_string(httpStatusCode) + " response, body length: " + std::to_string(response.body.length())); return response.body; } } // namespace RspCrypto