| #include "tlbmc/credentials/credential_manager_impl.h" |
| |
| #include <filesystem> // NOLINT |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <system_error> // NOLINT |
| #include <thread> // NOLINT |
| #include <utility> |
| |
| #include "owner_certificate/owner_verification_cert_configuration.pb.h" |
| #include "absl/functional/any_invocable.h" |
| #include "absl/log/log.h" |
| #include "absl/memory/memory.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/substitute.h" |
| #include "absl/synchronization/mutex.h" |
| #include "absl/time/clock.h" |
| #include "absl/time/time.h" |
| #include "zatar/x509_certificate.h" |
| #include "g3/macros.h" |
| #include "tlbmc/credentials/credential_manager.h" |
| #include "tlbmc/redfish/routes/action_managers/file_manager.h" |
| #include "resource.pb.h" |
| #include "tlbmc/resource/syslog_config_utils.h" |
| #include "tlbmc/utils/shell_command_executor.h" |
| #include "zatar/cert_error_handling.h" |
| #include "zatar/generate_cert.h" |
| #include "openssl/asn1.h" |
| #include "zatar/g3_misc.h" |
| #include "openssl/bio.h" |
| #include "openssl/bn.h" |
| #include "openssl/buffer.h" |
| #include "openssl/evp.h" |
| #include "openssl/pem.h" |
| #include "openssl/rsa.h" |
| #include "openssl/stack.h" |
| #include "openssl/x509.h" |
| #include "google/protobuf/text_format.h" |
| #include "re2/re2.h" |
| |
| namespace milotic_tlbmc { |
| |
| using ::milotic::authn::GeneratePrivateKey; |
| using ::milotic::authn::GetSslErrorOnQueue; |
| using ::milotic::authn::ReturnErrorIfNotSuccess; |
| using ::milotic::authn::ReturnErrorIfNull; |
| using ::owner_certificate::OwnerVerificationCertConfiguration; |
| using ::owner_certificate::SerialConsoleAccessLevel; |
| using ::google::protobuf::TextFormat; |
| |
| namespace { |
| |
| absl::Status AddNameEntry(X509_NAME* name, std::string_view entry, |
| std::string_view value) { |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNotSuccess( |
| X509_NAME_add_entry_by_txt( |
| name, std::string(entry).c_str(), MBSTRING_ASC, |
| reinterpret_cast<const unsigned char*>(value.data()), value.size(), |
| /*loc=*/-1, |
| /*set=*/0), |
| absl::StrCat("Failed to add name entry: ", value, "", |
| GetSslErrorOnQueue()))); |
| return absl::OkStatus(); |
| } |
| |
| std::string GetSyslogOverrideContent(const SyslogTargetConfig& config) { |
| std::string protocol_str = "tcp"; |
| auto protocol_yocto = GetYoctoStringFromSyslogProtocol(config.protocol()); |
| if (protocol_yocto.ok()) { |
| protocol_str = *protocol_yocto; |
| } else { |
| LOG(ERROR) << "Failed to convert protocol enum to Yocto string: " |
| << protocol_yocto.status(); |
| } |
| |
| std::string tls_mode_str = "tls"; |
| auto tls_mode_yocto = GetYoctoStringFromSyslogTlsMode(config.tls_mode()); |
| if (tls_mode_yocto.ok()) { |
| tls_mode_str = *tls_mode_yocto; |
| } else { |
| LOG(ERROR) << "Failed to convert TLS mode enum to Yocto string: " |
| << tls_mode_yocto.status(); |
| } |
| |
| return absl::StrFormat( |
| "[Service]\n" |
| "Environment=PROD_SYSLOG_IP=\"%s\"\n" |
| "Environment=PROD_SYSLOG_PORT=\"%d\"\n" |
| "Environment=PROD_SYSLOG_PROTO=\"%s\"\n" |
| "Environment=PROD_SYSLOG_TLS_MODE=\"%s\"\n" |
| "TimeoutStopSec=5\n", |
| config.target_ip(), static_cast<int>(config.target_port()), protocol_str, |
| tls_mode_str); |
| } |
| |
| } // namespace |
| |
| absl::StatusOr<std::unique_ptr<CredentialManagerImpl>> |
| CredentialManagerImpl::Create(const CredentialManagerParams& params) { |
| // Check that private key are non empty strings and are files in the same |
| // folder |
| if (params.private_key_path.empty()) { |
| return absl::InvalidArgumentError("Private key path is empty"); |
| } |
| if (params.cert_path.empty()) { |
| return absl::InvalidArgumentError("Cert path is empty"); |
| } |
| |
| // Check that the private key and cert are in the same folder |
| std::filesystem::path private_key_path_fs(params.private_key_path); |
| std::filesystem::path cert_path_fs(params.cert_path); |
| if (private_key_path_fs.parent_path() != cert_path_fs.parent_path()) { |
| return absl::InvalidArgumentError( |
| "Private key and cert are not in the same folder"); |
| } |
| |
| if (params.owner_verification_cert_path.empty()) { |
| return absl::InvalidArgumentError("Owner verification cert path is empty"); |
| } |
| |
| if (params.owner_verification_config_path.empty()) { |
| return absl::InvalidArgumentError( |
| "Owner verification config path is empty"); |
| } |
| |
| if (params.bmc_ssh_trusted_user_ca_keys_path.empty()) { |
| return absl::InvalidArgumentError( |
| "BMC SSH trusted user CA keys path is empty"); |
| } |
| |
| if (params.trust_bundle_path.empty()) { |
| return absl::InvalidArgumentError("Trust bundle path is empty"); |
| } |
| |
| if (params.trust_bundle_signature_path.empty()) { |
| return absl::InvalidArgumentError("Trust bundle signature path is empty"); |
| } |
| |
| if (params.os_verification_cert_path.empty()) { |
| return absl::InvalidArgumentError("OS verification cert path is empty"); |
| } |
| |
| if (params.os_verification_key_path.empty()) { |
| return absl::InvalidArgumentError("OS verification key path is empty"); |
| } |
| |
| if (params.serial_console_trusted_user_ca_keys_path.empty()) { |
| return absl::InvalidArgumentError( |
| "Serial console trusted user CA keys path is empty"); |
| } |
| |
| if (params.serial_console_detatched_signature_path.empty()) { |
| return absl::InvalidArgumentError( |
| "Serial console detatched signature path is empty"); |
| } |
| |
| if (params.serial_console_access_level_dir.empty()) { |
| return absl::InvalidArgumentError( |
| "Serial console access level path is empty"); |
| } |
| if (params.syslog_client_conf_override_path.empty()) { |
| return absl::InvalidArgumentError( |
| "Syslog client conf override path is empty"); |
| } |
| if (params.syslog_root_cert_path.empty()) { |
| return absl::InvalidArgumentError("Syslog root cert path is empty"); |
| } |
| |
| return absl::WrapUnique(new CredentialManagerImpl(params)); |
| } |
| |
| void CredentialManagerImpl::ParseOwnerVerificationCert() { |
| // If the write path already exists, then we should read from there. |
| absl::StatusOr<std::string> owner_verification_cert = |
| FileManager::ReadFile(params_.owner_verification_cert_path); |
| |
| if (!owner_verification_cert.ok()) { |
| LOG(ERROR) << "Failed to read owner verification certificate: " |
| << owner_verification_cert.status() |
| << ". All owner verification certificate validation will fail " |
| "as we will use an empty certificate."; |
| return; |
| } |
| |
| LOG(WARNING) << "Successfully read the owner verification certificate."; |
| owner_verification_cert_ = std::move(*owner_verification_cert); |
| } |
| |
| void CredentialManagerImpl::ParseOwnerVerificationConfiguration() { |
| // Reading the Owner Verification Config file |
| absl::StatusOr<std::string> owner_verification_config = |
| FileManager::ReadFile(params_.owner_verification_config_path); |
| if (!owner_verification_config.ok()) { |
| LOG(ERROR) << "Failed to read owner verification config: " |
| << owner_verification_config.status() |
| << ". Using default config which allows firmware updates."; |
| return; |
| } |
| // Parse the owner verification config file. |
| OwnerVerificationCertConfiguration owner_verification_cert_configuration; |
| TextFormat::Parser parser; |
| parser.AllowUnknownField(true); |
| if (!parser.ParseFromString(*owner_verification_config, |
| &owner_verification_cert_configuration)) { |
| LOG(ERROR) << "Failed to parse owner verification config. Using default " |
| "config which allows firmware updates."; |
| return; |
| } |
| owner_verification_cert_configuration_ = |
| std::move(owner_verification_cert_configuration); |
| |
| SerialConsoleAccessLevel serial_console_access_level = |
| owner_verification_cert_configuration_->serial_console_access_level(); |
| |
| absl::Status status; |
| switch (serial_console_access_level) { |
| case SerialConsoleAccessLevel::ACCESS_FULL: |
| break; |
| case SerialConsoleAccessLevel::ACCESS_DISABLE: |
| status = FileManager::WriteToFile( |
| "", absl::StrCat(params_.serial_console_access_level_dir, |
| "/serial_console_disabled")); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to write to serial console disabled file: " |
| << status; |
| } |
| break; |
| case SerialConsoleAccessLevel::ACCESS_READONLY: |
| status = FileManager::WriteToFile( |
| "", absl::StrCat(params_.serial_console_access_level_dir, |
| "/serial_console_readonly")); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to write to serial console readonly file: " |
| << status; |
| } |
| break; |
| default: |
| LOG(ERROR) << "Serial console access level is unknown."; |
| break; |
| } |
| } |
| |
| void CredentialManagerImpl::ParseBmcSshTrustedUserCaKeys() { |
| absl::StatusOr<std::string> bmc_ssh_trusted_user_ca_keys_signature = |
| FileManager::ReadFile(params_.bmc_ssh_trusted_user_ca_keys_path); |
| if (!bmc_ssh_trusted_user_ca_keys_signature.ok()) { |
| LOG(ERROR) << "Failed to read BMC SSH trusted user CA keys signature: " |
| << bmc_ssh_trusted_user_ca_keys_signature.status() |
| << ". Assuming no BMC SSH trusted user CA keys signature."; |
| return; |
| } |
| bmc_ssh_trusted_user_ca_keys_signature_ = |
| std::move(*bmc_ssh_trusted_user_ca_keys_signature); |
| } |
| |
| void CredentialManagerImpl::ParseTrustBundleFiles() { |
| absl::StatusOr<std::string> trust_bundle = |
| FileManager::ReadFile(params_.trust_bundle_path); |
| if (!trust_bundle.ok()) { |
| LOG(ERROR) << "Failed to read trust bundle: " << trust_bundle.status() |
| << ". Assuming no trust bundle."; |
| return; |
| } |
| trust_bundle_ = std::move(*trust_bundle); |
| |
| absl::StatusOr<std::string> trust_bundle_signature = |
| FileManager::ReadFile(params_.trust_bundle_signature_path); |
| if (!trust_bundle_signature.ok()) { |
| LOG(ERROR) << "Failed to read trust bundle signature: " |
| << trust_bundle_signature.status() |
| << ". Assuming no trust bundle signature."; |
| return; |
| } |
| trust_bundle_signature_ = std::move(*trust_bundle_signature); |
| } |
| |
| void CredentialManagerImpl::ParseServerCert() { |
| absl::StatusOr<std::string> server_cert = |
| FileManager::ReadFile(params_.cert_path); |
| if (!server_cert.ok()) { |
| LOG(ERROR) << "Failed to read server certificate: " << server_cert.status() |
| << ". Assuming no server certificate."; |
| return; |
| } |
| server_cert_ = std::move(*server_cert); |
| } |
| |
| void CredentialManagerImpl::ParseOsVerificationCert() { |
| absl::StatusOr<std::string> os_verification_cert = |
| FileManager::ReadFile(params_.os_verification_cert_path); |
| if (!os_verification_cert.ok()) { |
| LOG(ERROR) << "Failed to read OS verification certificate: " |
| << os_verification_cert.status() |
| << ". Assuming no OS verification certificate."; |
| return; |
| } |
| os_verification_cert_ = std::move(*os_verification_cert); |
| } |
| |
| void CredentialManagerImpl::ParseSerialConsoleTrustedUserCAKeys() { |
| absl::StatusOr<std::string> serial_console_trusted_user_ca_keys = |
| FileManager::ReadFile(params_.serial_console_trusted_user_ca_keys_path); |
| if (!serial_console_trusted_user_ca_keys.ok()) { |
| LOG(ERROR) << "Failed to read serial console trusted user CA keys: " |
| << serial_console_trusted_user_ca_keys.status() |
| << ". Assuming no serial console trusted user CA keys."; |
| } else { |
| serial_console_trusted_user_ca_keys_ = |
| std::move(*serial_console_trusted_user_ca_keys); |
| } |
| |
| absl::StatusOr<std::string> serial_console_detatched_signature = |
| FileManager::ReadFile(params_.serial_console_detatched_signature_path); |
| if (!serial_console_detatched_signature.ok()) { |
| LOG(ERROR) << "Failed to read serial console detatched signature: " |
| << serial_console_detatched_signature.status() |
| << ". Assuming no serial console detatched signature."; |
| return; |
| } |
| serial_console_detatched_signature_ = |
| std::move(*serial_console_detatched_signature); |
| } |
| |
| void CredentialManagerImpl::ParseSyslogTargetConfig() { |
| absl::StatusOr<std::string> syslog_client_conf_override = |
| FileManager::ReadFile(params_.syslog_client_conf_override_path); |
| if (!syslog_client_conf_override.ok()) { |
| LOG(ERROR) << "Failed to read syslog client conf override: " |
| << syslog_client_conf_override.status() |
| << ". Assuming no syslog client conf override."; |
| return; |
| } |
| |
| std::string target_ip; |
| int64_t target_port; |
| SyslogProtocol protocol = SYSLOG_PROTOCOL_TCP; |
| SyslogTlsMode tls_mode = SYSLOG_TLS_MODE_TLS; |
| if (RE2::PartialMatch(*syslog_client_conf_override, |
| "Environment=PROD_SYSLOG_IP=\"([^\"]+)\"", |
| &target_ip) && |
| RE2::PartialMatch(*syslog_client_conf_override, |
| "Environment=PROD_SYSLOG_PORT=\"(\\d+)\"", |
| &target_port)) { |
| std::string proto_str; |
| if (RE2::PartialMatch(*syslog_client_conf_override, |
| "Environment=PROD_SYSLOG_PROTO=\"([^\"]+)\"", |
| &proto_str)) { |
| auto proto_enum = GetSyslogProtocolFromYoctoString(proto_str); |
| if (proto_enum.ok()) { |
| protocol = *proto_enum; |
| } else { |
| LOG(ERROR) << "Failed to parse protocol from Yocto config: " |
| << proto_enum.status(); |
| } |
| } |
| std::string tls_mode_str; |
| if (RE2::PartialMatch(*syslog_client_conf_override, |
| "Environment=PROD_SYSLOG_TLS_MODE=\"([^\"]+)\"", |
| &tls_mode_str)) { |
| auto tls_mode_enum = GetSyslogTlsModeFromYoctoString(tls_mode_str); |
| if (tls_mode_enum.ok()) { |
| tls_mode = *tls_mode_enum; |
| } else { |
| LOG(ERROR) << "Failed to parse TLS mode from Yocto config: " |
| << tls_mode_enum.status(); |
| } |
| } |
| SyslogTargetConfig config; |
| config.set_target_ip(target_ip); |
| config.set_target_port(target_port); |
| config.set_protocol(protocol); |
| config.set_tls_mode(tls_mode); |
| syslog_cert_config_ = std::move(config); |
| } |
| } |
| |
| void CredentialManagerImpl::ParseSyslogRootCert() { |
| absl::StatusOr<std::string> syslog_root_cert = |
| FileManager::ReadFile(params_.syslog_root_cert_path); |
| if (!syslog_root_cert.ok()) { |
| LOG(ERROR) << "Failed to read syslog root cert: " |
| << syslog_root_cert.status() |
| << ". Assuming no syslog root cert."; |
| return; |
| } |
| syslog_root_cert_ = std::move(*syslog_root_cert); |
| } |
| |
| CredentialManagerImpl::CredentialManagerImpl( |
| const CredentialManagerParams& params) |
| : params_(params), command_executor_(std::make_unique<ShellExecutor>()) { |
| ParseOwnerVerificationCert(); |
| ParseOwnerVerificationConfiguration(); |
| ParseBmcSshTrustedUserCaKeys(); |
| ParseTrustBundleFiles(); |
| ParseServerCert(); |
| ParseOsVerificationCert(); |
| ParseSerialConsoleTrustedUserCAKeys(); |
| ParseSyslogTargetConfig(); |
| ParseSyslogRootCert(); |
| } |
| |
| namespace { |
| |
| absl::StatusOr<bssl::UniquePtr<X509_REQ>> CreateX509Req( |
| const CredentialManagerImpl::CsrParams& csr_params) { |
| bssl::UniquePtr<X509_REQ> x509(X509_REQ_new()); |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNull( |
| x509.get(), absl::StrCat("X509_REQ_new failed: ", GetSslErrorOnQueue()))); |
| |
| X509_NAME* subject = X509_REQ_get_subject_name(x509.get()); |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNull( |
| subject, absl::StrCat("Failed to get subject name from X509_REQ: ", |
| GetSslErrorOnQueue()))); |
| ECCLESIA_RETURN_IF_ERROR(AddNameEntry(subject, "C", csr_params.country)); |
| ECCLESIA_RETURN_IF_ERROR(AddNameEntry(subject, "ST", csr_params.state)); |
| ECCLESIA_RETURN_IF_ERROR(AddNameEntry(subject, "L", csr_params.city)); |
| ECCLESIA_RETURN_IF_ERROR(AddNameEntry(subject, "O", csr_params.organization)); |
| ECCLESIA_RETURN_IF_ERROR( |
| AddNameEntry(subject, "OU", csr_params.organizational_unit)); |
| ECCLESIA_RETURN_IF_ERROR(AddNameEntry(subject, "CN", csr_params.common_name)); |
| |
| if (!csr_params.alternative_names.empty()) { |
| std::string san_extension; |
| for (int i = 0; i < csr_params.alternative_names.size(); ++i) { |
| std::string_view san = csr_params.alternative_names[i]; |
| absl::StrAppend(&san_extension, "DNS:", san); |
| if (i < csr_params.alternative_names.size() - 1) { |
| absl::StrAppend(&san_extension, ","); |
| } |
| } |
| |
| bssl::UniquePtr<STACK_OF(X509_EXTENSION)> extensions( |
| sk_X509_EXTENSION_new_null()); |
| bssl::UniquePtr<X509_EXTENSION> san_ext(X509V3_EXT_conf_nid( |
| nullptr, nullptr, NID_subject_alt_name, san_extension.c_str())); |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNull( |
| san_ext.get(), absl::StrCat("Failed to create SAN extension: ", |
| GetSslErrorOnQueue()))); |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNotSuccess( |
| sk_X509_EXTENSION_push(extensions.get(), san_ext.release()), |
| absl::StrCat("Failed to add SAN extension to CSR: ", |
| GetSslErrorOnQueue()))); |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNotSuccess( |
| X509_REQ_add_extensions(x509.get(), extensions.get()), |
| absl::StrCat("Failed to add extensions to CSR: ", |
| GetSslErrorOnQueue()))); |
| } |
| return x509; |
| } |
| |
| absl::StatusOr<std::string> GenerateCsrWithPrivateKey( |
| const CredentialManagerImpl::CsrParams& csr_params, EVP_PKEY* private_key) { |
| ECCLESIA_ASSIGN_OR_RETURN(bssl::UniquePtr<X509_REQ> x509, |
| CreateX509Req(csr_params)); |
| |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNotSuccess( |
| X509_REQ_set_pubkey(x509.get(), private_key), |
| absl::StrCat("Failed to set public key in CSR: ", GetSslErrorOnQueue()))); |
| |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNotSuccess( |
| X509_REQ_sign(x509.get(), private_key, EVP_sha256()), |
| absl::StrCat("Failed to sign CSR: ", GetSslErrorOnQueue()))); |
| |
| bssl::UniquePtr<BIO> bio_csr(BIO_new(BIO_s_mem())); |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNotSuccess( |
| PEM_write_bio_X509_REQ(bio_csr.get(), x509.get()), |
| absl::StrCat("Failed to write CSR to BIO: ", GetSslErrorOnQueue()))); |
| char* csr = nullptr; |
| auto csr_len = BIO_get_mem_data(bio_csr.get(), &csr); |
| return std::string(csr, csr_len); |
| } |
| |
| } // namespace |
| |
| absl::StatusOr<std::string> CredentialManagerImpl::GenerateCsr( |
| const CsrParams& csr_params) { |
| ECCLESIA_ASSIGN_OR_RETURN(bssl::UniquePtr<EVP_PKEY> pkey, |
| GeneratePrivateKey()); |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNull( |
| pkey.get(), |
| absl::StrCat("Failed to generate private key: ", GetSslErrorOnQueue()))); |
| |
| ECCLESIA_ASSIGN_OR_RETURN(std::string csr, |
| GenerateCsrWithPrivateKey(csr_params, pkey.get())); |
| |
| { |
| absl::MutexLock lock(mutex_); |
| private_key_ = std::move(pkey); |
| } |
| return csr; |
| } |
| |
| absl::Status CredentialManagerImpl::InstallServerCert( |
| std::string_view certificate) { |
| absl::MutexLock lock(mutex_); |
| // If GenerateCSR has not been called, we cannot install the cert. |
| if (private_key_ == nullptr) { |
| return absl::InternalError( |
| "GenerateCSR must be called before InstallServerCert"); |
| } |
| |
| // 1. Load the certificate from the PEM string |
| bssl::UniquePtr<BIO> bio_cert(BIO_new_mem_buf( |
| certificate.data(), static_cast<uint32_t>(certificate.length()))); |
| bssl::UniquePtr<X509> cert( |
| PEM_read_bio_X509(bio_cert.get(), nullptr, nullptr, nullptr)); |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNull( |
| cert.get(), |
| absl::StrCat("Failed to parse certificate PEM: ", GetSslErrorOnQueue()))); |
| |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNotSuccess( |
| X509_check_private_key(cert.get(), private_key_.get()), |
| "Certificate does not match the private key")); |
| |
| bssl::UniquePtr<BIO> bio_privkey(BIO_new(BIO_s_mem())); |
| ECCLESIA_RETURN_IF_ERROR(ReturnErrorIfNotSuccess( |
| PEM_write_bio_PrivateKey(bio_privkey.get(), private_key_.get(), nullptr, |
| nullptr, 0, nullptr, nullptr), |
| absl::StrCat("PEM_write_bio_PrivateKey failed: ", GetSslErrorOnQueue()))); |
| |
| char* private_key = nullptr; |
| auto private_key_len = BIO_get_mem_data(bio_privkey.get(), &private_key); |
| std::string private_key_pem(private_key, private_key_len); |
| |
| // Install the cert and key on the machine. |
| // We must do directory name change here as the key and cert have to both be |
| // changed atomically |
| std::string credential_dir = |
| std::filesystem::path(params_.cert_path).parent_path(); |
| std::string cert_filename = |
| std::filesystem::path(params_.cert_path).filename(); |
| std::string private_key_filename = |
| std::filesystem::path(params_.private_key_path).filename(); |
| |
| // Write to a temp dir |
| std::string temp_credential_dir = absl::StrCat(credential_dir, "-tmp"); |
| if (params_.cert_path == params_.private_key_path) { |
| // After RestartBmcwebInASeparateThread is called below that bmcweb instance |
| // will attempt to read the cert and key. Since the filepaths are the same, |
| // we must write both the cert and key to the same file. |
| ECCLESIA_RETURN_IF_ERROR(FileManager::WriteToFile( |
| absl::StrCat(certificate, "\n", private_key_pem), |
| absl::StrCat(temp_credential_dir, "/", cert_filename))); |
| } else { |
| ECCLESIA_RETURN_IF_ERROR(FileManager::WriteToFile( |
| certificate, absl::StrCat(temp_credential_dir, "/", cert_filename))); |
| ECCLESIA_RETURN_IF_ERROR(FileManager::WriteToFile( |
| private_key_pem, |
| absl::StrCat(temp_credential_dir, "/", private_key_filename))); |
| } |
| |
| // Atomically change both key and cert |
| ECCLESIA_RETURN_IF_ERROR( |
| FileManager::RenamePath(temp_credential_dir, credential_dir, true)); |
| |
| RestartBmcwebInASeparateThread(); |
| |
| return absl::OkStatus(); |
| } |
| |
| void CredentialManagerImpl::RestartBmcwebInASeparateThread() { |
| std::thread([this]() { |
| absl::SleepFor(absl::Seconds(kServerRestartDelaySeconds)); |
| absl::StatusOr<std::string> status = |
| command_executor_->Execute(std::string(kRestartBmcWebCommand), false); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to restart bmcweb: " << status.status(); |
| } |
| if (restart_bmcweb_callback_ != nullptr) { |
| restart_bmcweb_callback_(status.status()); |
| } |
| }).detach(); |
| } |
| |
| absl::Status CredentialManagerImpl::InstallTrustBundle( |
| std::string_view trust_bundle, std::string_view signature) { |
| absl::MutexLock lock(mutex_); |
| if (last_install_time_ != absl::InfinitePast() && |
| absl::Now() - last_install_time_ < |
| absl::Seconds(kTrustBundleRetryDelaySeconds)) { |
| std::string error_message = absl::StrFormat( |
| "A trust bundle, installed at %s, is pending to be applied at %s. " |
| "Retry after %s.", |
| absl::FormatTime(last_install_time_), |
| absl::FormatTime(last_install_time_ + |
| absl::Seconds(kServerRestartDelaySeconds)), |
| absl::FormatTime(last_install_time_ + |
| absl::Seconds(kTrustBundleRetryDelaySeconds))); |
| LOG(ERROR) << error_message; |
| return absl::UnavailableError(error_message); |
| } |
| |
| ECCLESIA_ASSIGN_OR_RETURN(StagedTempFile staged_trust_bundle, |
| VerifyAndStageTrustBundle(trust_bundle, signature)); |
| |
| last_install_time_ = absl::Now(); |
| |
| ECCLESIA_RETURN_IF_ERROR( |
| FileManager::WriteToFile(trust_bundle, params_.trust_bundle_path)); |
| |
| ECCLESIA_RETURN_IF_ERROR( |
| FileManager::WriteToFile(signature, params_.trust_bundle_signature_path)); |
| |
| trust_bundle_ = std::string(trust_bundle); |
| trust_bundle_signature_ = std::string(signature); |
| |
| RestartBmcwebInASeparateThread(); |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::StatusOr<StagedTempFile> CredentialManagerImpl::VerifyAndStageTrustBundle( |
| std::string_view trust_bundle, std::string_view signature) { |
| ECCLESIA_ASSIGN_OR_RETURN( |
| StagedTempFile trust_bundle_file, |
| StagedTempFile::Create(trust_bundle, kTempTrustBundlePath)); |
| absl::Status status = |
| VerifyAndStageCertificate(trust_bundle_file.path(), signature); |
| LOG(WARNING) << "Trust bundle verify result: " << status.message(); |
| if (!status.ok()) { |
| return absl::Status( |
| status.code(), absl::StrCat("Failed to verify trust bundle signature. ", |
| status.message())); |
| } |
| return trust_bundle_file; |
| } |
| |
| absl::Status CredentialManagerImpl::VerifyAndStageCertificate( |
| std::string_view cert_path, std::string_view signature) { |
| std::error_code ec; |
| if (!std::filesystem::exists(params_.owner_verification_cert_path, ec)) { |
| return absl::FailedPreconditionError(absl::Substitute( |
| "Owner certificate file does not exist or can't be accessed: $0", |
| ec.message())); |
| } |
| |
| ECCLESIA_ASSIGN_OR_RETURN( |
| StagedTempFile signature_file, |
| StagedTempFile::Create(signature, kTempSignaturePath)); |
| std::string command = |
| absl::StrFormat(kTrustBundleVerifyWithCaCommand, signature_file.path(), |
| cert_path, params_.owner_verification_cert_path); |
| |
| return command_executor_->Execute(command, false).status(); |
| } |
| |
| absl::Status CredentialManagerImpl::InstallOsVerificationCertificate( |
| std::string_view cert_string) { |
| // Verify the OS verification certificate |
| ECCLESIA_RETURN_IF_ERROR(VerifyOsVerificationCertificate(cert_string)); |
| // Parse the public key |
| ECCLESIA_ASSIGN_OR_RETURN(std::string public_key, |
| ecclesia::GetPublicKey(cert_string)); |
| |
| // Write the cert itself to a file |
| ECCLESIA_RETURN_IF_ERROR( |
| FileManager::WriteToFile(cert_string, params_.os_verification_cert_path)); |
| |
| // Write the public key to a file |
| ECCLESIA_RETURN_IF_ERROR( |
| FileManager::WriteToFile(public_key, params_.os_verification_key_path)); |
| |
| os_verification_cert_ = std::string(cert_string); |
| |
| return absl::OkStatus(); |
| } |
| |
| absl::Status CredentialManagerImpl::VerifyOsVerificationCertificate( |
| std::string_view cert_string) { |
| absl::MutexLock lock(mutex_); |
| std::error_code ec; |
| if (!std::filesystem::exists(params_.owner_verification_cert_path, ec)) { |
| return absl::FailedPreconditionError(absl::Substitute( |
| "Owner certificate file does not exist or can't be accessed: $0", |
| ec.message())); |
| } |
| ECCLESIA_ASSIGN_OR_RETURN( |
| StagedTempFile os_cert_file, |
| StagedTempFile::Create(cert_string, kTempOsCertPath)); |
| std::string command = absl::StrFormat(kOsCertVerifyCommand, |
| params_.owner_verification_cert_path, |
| os_cert_file.path()); |
| absl::Status status = command_executor_->Execute(command, false).status(); |
| |
| if (!status.ok()) { |
| return absl::Status( |
| status.code(), |
| absl::StrCat("Failed to verify OS verification certificate. ", |
| status.message())); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status |
| CredentialManagerImpl::VerifyAndStageSerialConsoleTrustedUserCAKeys( |
| std::string_view trusted_user_ca_keys, std::string_view signature) { |
| ECCLESIA_ASSIGN_OR_RETURN( |
| StagedTempFile trusted_user_ca_keys_file, |
| StagedTempFile::Create(trusted_user_ca_keys, kTempTrustedUserCAKeysPath)); |
| absl::Status status = |
| VerifyAndStageCertificate(trusted_user_ca_keys_file.path(), signature); |
| LOG(WARNING) << "Trusted user CA keys verify result: " << status.message(); |
| if (!status.ok()) { |
| return absl::Status( |
| status.code(), |
| absl::StrCat("Failed to verify trusted user CA keys signature. ", |
| status.message())); |
| } |
| return absl::OkStatus(); |
| } |
| |
| absl::Status CredentialManagerImpl::InstallSerialConsoleTrustedUserCAKeys( |
| std::string_view trusted_user_ca_keys, std::string_view signature) { |
| absl::MutexLock lock(mutex_); |
| ECCLESIA_RETURN_IF_ERROR(VerifyAndStageSerialConsoleTrustedUserCAKeys( |
| trusted_user_ca_keys, signature)); |
| ECCLESIA_RETURN_IF_ERROR(FileManager::WriteToFile( |
| trusted_user_ca_keys, params_.serial_console_trusted_user_ca_keys_path)); |
| ECCLESIA_RETURN_IF_ERROR(FileManager::WriteToFile( |
| signature, params_.serial_console_detatched_signature_path)); |
| serial_console_trusted_user_ca_keys_ = std::string(trusted_user_ca_keys); |
| serial_console_detatched_signature_ = std::string(signature); |
| return absl::OkStatus(); |
| } |
| |
| absl::Status CredentialManagerImpl::InstallSyslogCert( |
| std::string_view cert_string, std::string_view target_ip, |
| int64_t target_port, SyslogProtocol protocol, SyslogTlsMode tls_mode) { |
| absl::MutexLock lock(mutex_); |
| std::optional<SyslogTargetConfig> config_orig = syslog_cert_config_; |
| |
| if (target_ip.empty() && (target_port > 0) && (target_port <= 65535)) { |
| return absl::InvalidArgumentError("Target IP is empty."); |
| } |
| if (!target_ip.empty() && ((target_port <= 0) || (target_port > 65535))) { |
| return absl::InvalidArgumentError("Target port is empty."); |
| } |
| |
| if (protocol == SYSLOG_PROTOCOL_UDP && tls_mode != SYSLOG_TLS_MODE_DISABLED) { |
| return absl::InvalidArgumentError( |
| "TLS is not supported when protocol is UDP."); |
| } |
| |
| if (!target_ip.empty() && (target_port != 0)) { |
| SyslogTargetConfig new_config; |
| new_config.set_target_ip(target_ip); |
| new_config.set_target_port(target_port); |
| new_config.set_protocol(protocol); |
| new_config.set_tls_mode(tls_mode); |
| std::string override_content = GetSyslogOverrideContent(new_config); |
| |
| ECCLESIA_RETURN_IF_ERROR(FileManager::WriteToFile( |
| override_content, params_.syslog_client_conf_override_path)); |
| syslog_cert_config_ = std::move(new_config); |
| } |
| |
| absl::Status status = |
| FileManager::WriteToFile(cert_string, params_.syslog_root_cert_path); |
| if (!status.ok()) { |
| // If cert file write fails, revert client conf override file. |
| if (config_orig.has_value()) { |
| absl::Status revert_status; |
| revert_status = |
| FileManager::WriteToFile(GetSyslogOverrideContent(*config_orig), |
| params_.syslog_client_conf_override_path); |
| if (!revert_status.ok()) { |
| LOG(ERROR) << "Failed to revert client conf override file: " |
| << revert_status; |
| } |
| syslog_cert_config_ = std::move(config_orig); |
| } |
| return status; |
| } |
| syslog_root_cert_ = std::string(cert_string); |
| return absl::OkStatus(); |
| } |
| |
| } // namespace milotic_tlbmc |