1 #include "certs_manager.hpp" 2 3 #include <openssl/bio.h> 4 #include <openssl/crypto.h> 5 #include <openssl/err.h> 6 #include <openssl/evp.h> 7 #include <openssl/pem.h> 8 #include <openssl/x509v3.h> 9 10 #include <experimental/filesystem> 11 #include <sdbusplus/bus.hpp> 12 #include <xyz/openbmc_project/Common/error.hpp> 13 14 namespace phosphor 15 { 16 namespace certs 17 { 18 // RAII support for openSSL functions. 19 using BIO_MEM_Ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>; 20 using X509_STORE_CTX_Ptr = 21 std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>; 22 using X509_LOOKUP_Ptr = 23 std::unique_ptr<X509_LOOKUP, decltype(&::X509_LOOKUP_free)>; 24 using EVP_PKEY_Ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>; 25 26 namespace fs = std::experimental::filesystem; 27 using InternalFailure = 28 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 29 // Trust chain related errors.` 30 #define TRUST_CHAIN_ERR(errnum) \ 31 ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || \ 32 (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) || \ 33 (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) || \ 34 (errnum == X509_V_ERR_CERT_UNTRUSTED) || \ 35 (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE)) 36 37 void Manager::install(const std::string path) 38 { 39 // Verify the certificate file 40 auto rc = verifyCert(path); 41 // Allow certificate upload, for "certificate is not yet valid" and 42 // trust chain related errors. 43 if (!((rc == X509_V_OK) || (rc == X509_V_ERR_CERT_NOT_YET_VALID) || 44 TRUST_CHAIN_ERR(rc))) 45 { 46 if (rc == X509_V_ERR_CERT_HAS_EXPIRED) 47 { 48 elog<InvalidCertificate>(Reason("Expired Certificate")); 49 } 50 // Loging general error here. 51 elog<InvalidCertificate>(Reason("Certificate validation failed")); 52 } 53 54 // Invoke type specific install function. 55 auto iter = typeFuncMap.find(type); 56 if (iter == typeFuncMap.end()) 57 { 58 log<level::ERR>("Unsupported Type", entry("TYPE=%s", type.c_str())); 59 elog<InternalFailure>(); 60 } 61 iter->second(path); 62 63 // Copy the certificate file 64 copy(path, certPath); 65 66 if (!unit.empty()) 67 { 68 reloadOrReset(unit); 69 } 70 } 71 72 void Manager::reloadOrReset(const std::string& unit) 73 { 74 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 75 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; 76 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 77 78 try 79 { 80 auto method = 81 bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 82 SYSTEMD_INTERFACE, "ReloadOrRestartUnit"); 83 84 method.append(unit, "replace"); 85 86 bus.call_noreply(method); 87 } 88 catch (const sdbusplus::exception::SdBusError& e) 89 { 90 log<level::ERR>("Failed to reload or restart service", 91 entry("ERR=%s", e.what()), 92 entry("UNIT=%s", unit.c_str())); 93 elog<InternalFailure>(); 94 } 95 } 96 97 void Manager::copy(const std::string& src, const std::string& dst) 98 { 99 try 100 { 101 auto path = fs::path(dst).parent_path(); 102 // create dst path folder by default 103 fs::create_directories(path); 104 fs::copy_file(src, dst, fs::copy_options::overwrite_existing); 105 } 106 catch (fs::filesystem_error& e) 107 { 108 log<level::ERR>("Failed to copy certificate", entry("ERR=%s", e.what()), 109 entry("SRC=%s", src.c_str()), 110 entry("DST=%s", dst.c_str())); 111 elog<InternalFailure>(); 112 } 113 } 114 115 X509_Ptr Manager::loadCert(const std::string& filePath) 116 { 117 // Read Certificate file 118 X509_Ptr cert(X509_new(), ::X509_free); 119 if (!cert) 120 { 121 log<level::ERR>("Error occured during X509_new call", 122 entry("FILE=%s", filePath.c_str()), 123 entry("ERRCODE=%lu", ERR_get_error())); 124 elog<InternalFailure>(); 125 } 126 127 BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free); 128 if (!bioCert) 129 { 130 log<level::ERR>("Error occured during BIO_new_file call", 131 entry("FILE=%s", filePath.c_str())); 132 elog<InternalFailure>(); 133 } 134 135 X509* x509 = cert.get(); 136 if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr)) 137 { 138 log<level::ERR>("Error occured during PEM_read_bio_X509 call", 139 entry("FILE=%s", filePath.c_str())); 140 elog<InternalFailure>(); 141 } 142 return cert; 143 } 144 145 int32_t Manager::verifyCert(const std::string& filePath) 146 { 147 auto errCode = X509_V_OK; 148 149 fs::path file(filePath); 150 if (!fs::exists(file)) 151 { 152 log<level::ERR>("File is Missing", entry("FILE=%s", filePath.c_str())); 153 elog<InternalFailure>(); 154 } 155 156 try 157 { 158 if (fs::file_size(filePath) == 0) 159 { 160 // file is empty 161 log<level::ERR>("File is empty", 162 entry("FILE=%s", filePath.c_str())); 163 elog<InvalidCertificate>(Reason("File is empty")); 164 } 165 } 166 catch (const fs::filesystem_error& e) 167 { 168 // Log Error message 169 log<level::ERR>(e.what(), entry("FILE=%s", filePath.c_str())); 170 elog<InternalFailure>(); 171 } 172 173 // Defining store object as RAW to avoid double free. 174 // X509_LOOKUP_free free up store object. 175 // Create an empty X509_STORE structure for certificate validation. 176 auto x509Store = X509_STORE_new(); 177 if (!x509Store) 178 { 179 log<level::ERR>("Error occured during X509_STORE_new call"); 180 elog<InternalFailure>(); 181 } 182 183 OpenSSL_add_all_algorithms(); 184 185 // ADD Certificate Lookup method. 186 X509_LOOKUP_Ptr lookup(X509_STORE_add_lookup(x509Store, X509_LOOKUP_file()), 187 ::X509_LOOKUP_free); 188 if (!lookup) 189 { 190 // Normally lookup cleanup function interanlly does X509Store cleanup 191 // Free up the X509Store. 192 X509_STORE_free(x509Store); 193 log<level::ERR>("Error occured during X509_STORE_add_lookup call"); 194 elog<InternalFailure>(); 195 } 196 // Load Certificate file. 197 int32_t rc = X509_LOOKUP_load_file(lookup.get(), filePath.c_str(), 198 X509_FILETYPE_PEM); 199 if (rc != 1) 200 { 201 log<level::ERR>("Error occured during X509_LOOKUP_load_file call", 202 entry("FILE=%s", filePath.c_str())); 203 elog<InvalidCertificate>(Reason("Invalid certificate file format")); 204 } 205 206 // Load Certificate file into the X509 structre. 207 X509_Ptr cert = std::move(loadCert(filePath)); 208 X509_STORE_CTX_Ptr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free); 209 if (!storeCtx) 210 { 211 log<level::ERR>("Error occured during X509_STORE_CTX_new call", 212 entry("FILE=%s", filePath.c_str())); 213 elog<InternalFailure>(); 214 } 215 216 rc = X509_STORE_CTX_init(storeCtx.get(), x509Store, cert.get(), NULL); 217 if (rc != 1) 218 { 219 log<level::ERR>("Error occured during X509_STORE_CTX_init call", 220 entry("FILE=%s", filePath.c_str())); 221 elog<InternalFailure>(); 222 } 223 224 // Set time to current time. 225 auto locTime = time(nullptr); 226 227 X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME, 228 locTime); 229 230 rc = X509_verify_cert(storeCtx.get()); 231 if (rc == 1) 232 { 233 errCode = X509_V_OK; 234 } 235 else if (rc == 0) 236 { 237 errCode = X509_STORE_CTX_get_error(storeCtx.get()); 238 log<level::ERR>("Certificate verification failed", 239 entry("FILE=%s", filePath.c_str()), 240 entry("ERRCODE=%d", errCode)); 241 } 242 else 243 { 244 log<level::ERR>("Error occured during X509_verify_cert call", 245 entry("FILE=%s", filePath.c_str())); 246 elog<InternalFailure>(); 247 } 248 return errCode; 249 } 250 251 bool Manager::compareKeys(const std::string& filePath) 252 { 253 X509_Ptr cert(X509_new(), ::X509_free); 254 if (!cert) 255 { 256 log<level::ERR>("Error occured during X509_new call", 257 entry("FILE=%s", filePath.c_str()), 258 entry("ERRCODE=%lu", ERR_get_error())); 259 elog<InternalFailure>(); 260 } 261 262 BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free); 263 if (!bioCert) 264 { 265 log<level::ERR>("Error occured during BIO_new_file call", 266 entry("FILE=%s", filePath.c_str())); 267 elog<InternalFailure>(); 268 } 269 270 X509* x509 = cert.get(); 271 PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr); 272 273 EVP_PKEY_Ptr pubKey(X509_get_pubkey(cert.get()), ::EVP_PKEY_free); 274 if (!pubKey) 275 { 276 log<level::ERR>("Error occurred during X509_get_pubkey", 277 entry("FILE=%s", filePath.c_str()), 278 entry("ERRCODE=%lu", ERR_get_error())); 279 elog<InvalidCertificate>(Reason("Failed to get public key info")); 280 } 281 282 BIO_MEM_Ptr keyBio(BIO_new(BIO_s_file()), ::BIO_free); 283 if (!keyBio) 284 { 285 log<level::ERR>("Error occured during BIO_s_file call", 286 entry("FILE=%s", filePath.c_str())); 287 elog<InternalFailure>(); 288 } 289 BIO_read_filename(keyBio.get(), filePath.c_str()); 290 291 EVP_PKEY_Ptr priKey( 292 PEM_read_bio_PrivateKey(keyBio.get(), nullptr, nullptr, nullptr), 293 ::EVP_PKEY_free); 294 295 if (!priKey) 296 { 297 log<level::ERR>("Error occurred during PEM_read_bio_PrivateKey", 298 entry("FILE=%s", filePath.c_str()), 299 entry("ERRCODE=%lu", ERR_get_error())); 300 elog<InvalidCertificate>(Reason("Failed to get private key info")); 301 } 302 303 int32_t rc = EVP_PKEY_cmp(priKey.get(), pubKey.get()); 304 if (rc != 1) 305 { 306 log<level::ERR>("Private key is not matching with Certificate", 307 entry("FILE=%s", filePath.c_str()), 308 entry("ERRCODE=%d", rc)); 309 return false; 310 } 311 312 return true; 313 } 314 315 void Manager::delete_() 316 { 317 try 318 { 319 if (!fs::remove(certPath)) 320 { 321 log<level::INFO>("Certificate file not found!", 322 entry("PATH=%s", certPath.c_str())); 323 } 324 else if (!unit.empty()) 325 { 326 reloadOrReset(unit); 327 } 328 } 329 catch (const InternalFailure& e) 330 { 331 throw; 332 } 333 catch (const std::exception& e) 334 { 335 log<level::ERR>( 336 "Failed to delete certificate", entry("UNIT=%s", unit.c_str()), 337 entry("ERR=%s", e.what()), entry("PATH=%s", certPath.c_str())); 338 elog<InternalFailure>(); 339 } 340 } 341 342 } // namespace certs 343 } // namespace phosphor 344