Add in-tree nvidia-ipmi-oem provider

This patch adds nvidia-ipmi-oem. The current patch adds support
to 2 commands:
1) GET BIOS PASSWORD
2) SET BIOS PASSWORD

Tested:
1) Tested on patch
https://gerrit.openbmc.org/c/openbmc/openbmc/+/80748

Change-Id: I48599a1b85a3a614c56e7687b8646f7dfe1e7bb1
Signed-off-by: Prithvi Pai <ppai@nvidia.com>
diff --git a/OWNERS b/OWNERS
index 432ca86..a9dd7fb 100644
--- a/OWNERS
+++ b/OWNERS
@@ -45,6 +45,9 @@
 - partial_regex: oem/example
   owners:
   - vernon.mauery@gmail.com
+- partial_regex: oem/nvidia
+  owners:
+  - prithvi24pai@gmail.com
 
 openbmc:
 - name: Adriana Kobylak
diff --git a/meson.options b/meson.options
index ed2a4f3..76fbdd8 100644
--- a/meson.options
+++ b/meson.options
@@ -280,6 +280,7 @@
         # keep list below sorted alphabetically
         #######################################
         'example',
+        'nvidia',
     ],
     value: ['all'],
     description: 'Build selected OEM IPMI provider libraries',
diff --git a/oem/meson.build b/oem/meson.build
index 9bae274..54a85ba 100644
--- a/oem/meson.build
+++ b/oem/meson.build
@@ -8,3 +8,7 @@
 if 'example' in oem_opt or 'all' in oem_opt
     subdir('example')
 endif
+
+if 'nvidia' in oem_opt or 'all' in oem_opt
+    subdir('nvidia')
+endif
diff --git a/oem/nvidia/biosconfigcommands.cpp b/oem/nvidia/biosconfigcommands.cpp
new file mode 100644
index 0000000..a4ed842
--- /dev/null
+++ b/oem/nvidia/biosconfigcommands.cpp
@@ -0,0 +1,183 @@
+#include "config.h"
+
+#include "oemcommands.hpp"
+
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+#include <openssl/sha.h>
+
+#include <ipmid/api.hpp>
+#include <ipmid/types.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <array>
+#include <cstdint>
+#include <fstream>
+#include <string>
+#include <vector>
+
+constexpr char biosPasswordFilePath[] =
+    "/var/lib/bios-settings-manager/seedData";
+constexpr int biosPasswordIter = 1000;
+constexpr uint8_t biosPasswordSaltSize = 32;
+constexpr uint8_t biosPasswordMaxHashSize = 64;
+constexpr uint8_t biosPasswordTypeNoChange = 0x00;
+constexpr uint8_t biosPasswordSelectorAdmin = 0x01;
+constexpr uint8_t biosPasswordTypeNoPassowrd = 0x01;
+constexpr uint8_t biosPasswordTypePbkdf2Sha256 = 0x02;
+constexpr uint8_t biosPasswordTypePbkdf2Sha384 = 0x03;
+
+void registerBiosConfigCommands() __attribute__((constructor));
+
+namespace ipmi
+{
+ipmi::RspType<> ipmiSetBiosPassword(
+    uint8_t id, uint8_t type, std::array<uint8_t, biosPasswordSaltSize> salt,
+    std::array<uint8_t, biosPasswordMaxHashSize> hash)
+{
+    nlohmann::json json;
+
+    if (id != biosPasswordSelectorAdmin)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+    // key names for json object
+    constexpr char keyHashAlgo[] = "HashAlgo";
+    constexpr char keySeed[] = "Seed";
+    constexpr char keyAdminPwdHash[] = "AdminPwdHash";
+    constexpr char keyIsAdminPwdChanged[] = "IsAdminPwdChanged";
+    constexpr char keyIsUserPwdChanged[] = "IsUserPwdChanged";
+    constexpr char keyUserPwdHash[] = "UserPwdHash";
+
+    switch (type)
+    {
+        case biosPasswordTypeNoPassowrd:
+            json[keyHashAlgo] = "SHA256";
+            RAND_bytes(salt.data(), salt.size());
+            // password is only Null-terminated character
+            PKCS5_PBKDF2_HMAC("", 1, salt.data(), salt.size(), biosPasswordIter,
+                              EVP_sha256(), SHA256_DIGEST_LENGTH, hash.data());
+            json[keySeed] = salt;
+            json[keyAdminPwdHash] = hash;
+            break;
+        case biosPasswordTypePbkdf2Sha256:
+            json[keyHashAlgo] = "SHA256";
+            json[keySeed] = salt;
+            json[keyAdminPwdHash] = hash;
+            break;
+        case biosPasswordTypePbkdf2Sha384:
+            json[keyHashAlgo] = "SHA384";
+            json[keySeed] = salt;
+            json[keyAdminPwdHash] = hash;
+            break;
+        default:
+            return ipmi::responseInvalidFieldRequest();
+    }
+
+    json[keyIsAdminPwdChanged] = false;
+    json[keyIsUserPwdChanged] = false;
+
+    // initializing with 0 as user password hash field
+    // is not used presently
+    constexpr std::array<uint8_t, biosPasswordMaxHashSize> userPwdHash = {0};
+    json[keyUserPwdHash] = userPwdHash;
+
+    try
+    {
+        std::ofstream ofs(biosPasswordFilePath, std::ios::out);
+        const auto& writeData = json.dump();
+        ofs << writeData;
+        ofs.close();
+    }
+    catch (std::exception& e)
+    {
+        lg2::error("Failed to save BIOS Password information: {ERROR}", "ERROR",
+                   e.what());
+        return ipmi::responseUnspecifiedError();
+    }
+    return ipmi::responseSuccess();
+}
+
+ipmi::RspType<uint8_t,                                     // action
+              std::array<uint8_t, biosPasswordSaltSize>,   // salt
+              std::array<uint8_t, biosPasswordMaxHashSize> // hash
+              >
+    ipmiGetBiosPassword(uint8_t id)
+{
+    uint8_t action = biosPasswordTypeNoChange;
+    std::array<uint8_t, biosPasswordSaltSize> salt = {0};
+    std::array<uint8_t, biosPasswordMaxHashSize> hash = {0};
+
+    if (id != biosPasswordSelectorAdmin)
+    {
+        return ipmi::responseParmOutOfRange();
+    }
+
+    std::ifstream ifs(biosPasswordFilePath);
+    if (!ifs.is_open())
+    {
+        // return No change if no file
+        return ipmi::responseSuccess(action, salt, hash);
+    }
+
+    // key names for json object
+    constexpr char keyIsAdminPwdChanged[] = "IsAdminPwdChanged";
+    constexpr char keyHashAlgo[] = "HashAlgo";
+    constexpr char keySeed[] = "Seed";
+    constexpr char keyAdminPwdHash[] = "AdminPwdHash";
+
+    nlohmann::json json = nlohmann::json::parse(ifs, nullptr, false);
+    if (json.is_discarded() || !json.contains(keyIsAdminPwdChanged) ||
+        !json.contains(keyHashAlgo) || !json.contains(keySeed) ||
+        !json.contains(keyAdminPwdHash))
+    {
+        return ipmi::responseResponseError();
+    }
+    bool IsAdminPwdChanged = json[keyIsAdminPwdChanged];
+    if (IsAdminPwdChanged == false)
+    {
+        return ipmi::responseSuccess(action, salt, hash);
+    }
+
+    salt = json[keySeed];
+    hash = json[keyAdminPwdHash];
+
+    std::string HashAlgo = json[keyHashAlgo];
+    auto digest = EVP_sha256();
+    int keylen = SHA256_DIGEST_LENGTH;
+
+    if (HashAlgo == "SHA256")
+    {
+        action = biosPasswordTypePbkdf2Sha256;
+    }
+    else if (HashAlgo == "SHA384")
+    {
+        action = biosPasswordTypePbkdf2Sha384;
+        digest = EVP_sha384();
+        keylen = SHA384_DIGEST_LENGTH;
+    }
+
+    std::array<uint8_t, biosPasswordMaxHashSize> nullHash = {0};
+    PKCS5_PBKDF2_HMAC("", 1, salt.data(), salt.size(), biosPasswordIter, digest,
+                      keylen, nullHash.data());
+    if (hash == nullHash)
+    {
+        action = biosPasswordTypeNoPassowrd;
+        salt.fill(0x00);
+        hash.fill(0x00);
+    }
+
+    return ipmi::responseSuccess(action, salt, hash);
+}
+} // namespace ipmi
+
+void registerBiosConfigCommands()
+{
+    ipmi::registerHandler(ipmi::prioOemBase, ipmi::groupNvidia,
+                          ipmi::bios_password::cmdSetBiosPassword,
+                          ipmi::Privilege::Admin, ipmi::ipmiSetBiosPassword);
+    ipmi::registerHandler(ipmi::prioOemBase, ipmi::groupNvidia,
+                          ipmi::bios_password::cmdGetBiosPassword,
+                          ipmi::Privilege::Admin, ipmi::ipmiGetBiosPassword);
+}
diff --git a/oem/nvidia/meson.build b/oem/nvidia/meson.build
new file mode 100644
index 0000000..f22c98e
--- /dev/null
+++ b/oem/nvidia/meson.build
@@ -0,0 +1,26 @@
+# nvidia OEM library build
+
+nvidia_oem_src = ['biosconfigcommands.cpp']
+
+nvidia_oem_deps = [
+    boost,
+    crypto,
+    ipmid_dep,
+    nlohmann_json_dep,
+    phosphor_dbus_interfaces_dep,
+    phosphor_logging_dep,
+    sdbusplus_dep,
+    stdplus_dep,
+]
+
+
+nvidia_ipmi_oem = library(
+    'nvidia_ipmi_oem',
+    nvidia_oem_src,
+    dependencies: nvidia_oem_deps,
+    include_directories: [root_inc],
+    install: true,
+    install_dir: get_option('libdir') / 'ipmid-providers',
+    version: meson.project_version(),
+    override_options: ipmi_plugin_options,
+)
diff --git a/oem/nvidia/oemcommands.hpp b/oem/nvidia/oemcommands.hpp
new file mode 100644
index 0000000..2aa67ee
--- /dev/null
+++ b/oem/nvidia/oemcommands.hpp
@@ -0,0 +1,15 @@
+#pragma once
+#include <cstdint>
+
+namespace ipmi
+{
+
+using Group = uint8_t;
+constexpr Group groupNvidia = 0x3C;
+
+namespace bios_password
+{
+constexpr auto cmdSetBiosPassword = 0x36;
+constexpr auto cmdGetBiosPassword = 0x37;
+} // namespace bios_password
+} // namespace ipmi