Skip to content

Commit

Permalink
Property for model cache encryption/decryption functions (openvinotoo…
Browse files Browse the repository at this point in the history
…lkit#24417)

### Details:
 - *add new property for model cache encryption/decryption function*
- *encrypt/decrypt topology in CPU model cache if the callbacks are
provided.*

### Tickets:
 - *CVS-139600*

---------

Co-authored-by: Chen Peter <[email protected]>
  • Loading branch information
xufang-lisa and peterchen-intel authored Aug 28, 2024
1 parent f7435e4 commit 4d716ba
Show file tree
Hide file tree
Showing 29 changed files with 385 additions and 19 deletions.
19 changes: 19 additions & 0 deletions src/bindings/c/include/openvino/c/ov_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,25 @@ typedef enum {
F8E8M0, //!< f8e8m0 element type
} ov_element_type_e;

/**
* @brief encryption_func is a function pointer that encrypt or decrypt the input memory, example of this function is
* codec(const char* input, const size_t in_size, const char* output, size_t* out_size)
* This function needs to be called twice,
* the first call to obtain out_size (the size of output buffer), the second call to obtain output buffer.
* The first call output is nullptr, before the second call, the caller needs to apply for output
* memory based on the out_size returned by the first call.
* the memory of parameter output is allocated and released by the caller.
* @param input The pointer to the input buffer.
* @param in_size The size of input.
* @param output The pointer to the encrypted/decrypted buffer.
* @param out_size The size of output.
*/
typedef void (*encryption_func)(const char*, const size_t, char*, size_t*);
typedef struct {
encryption_func encrypt_func; // encryption function pointer
encryption_func decrypt_func; // decryption function pointer
} ov_encryption_callbacks;

/**
* @brief Print the error info.
* @ingroup ov_base_c_api
Expand Down
9 changes: 9 additions & 0 deletions src/bindings/c/include/openvino/c/ov_property.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ ov_property_key_cache_dir;
OPENVINO_C_VAR(const char*)
ov_property_key_cache_mode;

/**
* @brief Write-only property<ov_encryption_callbacks*> to set encryption/decryption function for model cache.
* If ov_property_key_cache_encryption_callbacks is set, model cache will be encrypted/decrypted when saving/loading
* model cache. ov_property_key_cache_encryption_callbacks is enabled in ov_core_compile_model_* only
* @ingroup ov_property_c_api
*/
OPENVINO_C_VAR(const char*)
ov_property_key_cache_encryption_callbacks;

/**
* @brief Read-write property<uint32_t string> to set/get the number of executor logical partitions.
* @ingroup ov_property_c_api
Expand Down
47 changes: 42 additions & 5 deletions src/bindings/c/src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <streambuf>
#include <string>

#include "openvino/c/ov_common.h"
#include "openvino/core/except.hpp"
#include "openvino/openvino.hpp"
#include "openvino/runtime/exception.hpp"
Expand All @@ -30,11 +31,47 @@
return ov_status_e::UNKNOW_EXCEPTION; \
}

#define GET_PROPERTY_FROM_ARGS_LIST \
std::string property_key = va_arg(args_ptr, char*); \
std::string _value = va_arg(args_ptr, char*); \
ov::Any value = _value; \
property[property_key] = value;
#define GET_PROPERTY_FROM_ARGS_LIST \
std::string property_key = va_arg(args_ptr, char*); \
if (property_key == ov::cache_encryption_callbacks.name()) { \
ov_encryption_callbacks* _value = va_arg(args_ptr, ov_encryption_callbacks*); \
auto encrypt_func = _value->encrypt_func; \
auto decrypt_func = _value->decrypt_func; \
std::function<std::string(const std::string&)> encrypt_value = [encrypt_func](const std::string& in) { \
size_t out_size = 0; \
std::string out_str; \
encrypt_func(in.c_str(), in.length(), nullptr, &out_size); \
if (out_size > 0) { \
std::unique_ptr<char[]> output_ptr(new char[out_size]); \
if (output_ptr) { \
char* output = output_ptr.get(); \
encrypt_func(in.c_str(), in.length(), output, &out_size); \
out_str.assign(output, out_size); \
} \
} \
return out_str; \
}; \
std::function<std::string(const std::string&)> decrypt_value = [decrypt_func](const std::string& in) { \
size_t out_size = 0; \
std::string out_str; \
decrypt_func(in.c_str(), in.length(), nullptr, &out_size); \
if (out_size > 0) { \
std::unique_ptr<char[]> output_ptr(new char[out_size]); \
if (output_ptr) { \
char* output = output_ptr.get(); \
decrypt_func(in.c_str(), in.length(), output, &out_size); \
out_str.assign(output, out_size); \
} \
} \
return out_str; \
}; \
ov::EncryptionCallbacks encryption_callbacks{encrypt_value, decrypt_value}; \
property[property_key] = encryption_callbacks; \
} else { \
std::string _value = va_arg(args_ptr, char*); \
ov::Any value = _value; \
property[property_key] = value; \
}

/**
* @struct ov_core
Expand Down
5 changes: 4 additions & 1 deletion src/bindings/c/src/ov_property.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ const char* ov_property_key_device_priorities = "MULTI_DEVICE_PRIORITIES";
const char* ov_property_key_hint_execution_mode = "EXECUTION_MODE_HINT";
const char* ov_property_key_force_tbb_terminate = "FORCE_TBB_TERMINATE";
const char* ov_property_key_enable_mmap = "ENABLE_MMAP";
const char* ov_property_key_auto_batch_timeout = "AUTO_BATCH_TIMEOUT";
const char* ov_property_key_auto_batch_timeout = "AUTO_BATCH_TIMEOUT";

// Write-only property key
const char* ov_property_key_cache_encryption_callbacks = "CACHE_ENCRYPTION_CALLBACKS";
56 changes: 56 additions & 0 deletions src/bindings/c/tests/ov_core_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,62 @@ TEST_P(ov_core_test, ov_core_import_model) {
ov_core_free(core);
}

static const char codec_key[] = {0x30, 0x60, 0x70, 0x02, 0x04, 0x08, 0x3F, 0x6F, 0x72, 0x74, 0x78, 0x7F};

static void codec_xor(const char* in, const size_t in_size, char* out, size_t* out_size) {
if (!out || *out_size < in_size) {
*out_size = in_size;
return;
}
size_t key_size = sizeof(codec_key);
for (size_t i = 0; i < in_size; i++) {
out[i] = in[i] ^ codec_key[i % key_size];
}
*out_size = in_size;
}

TEST_P(ov_core_test, ov_core_import_model_with_encryption) {
auto device_name = GetParam();
ov_core_t* core = nullptr;

OV_EXPECT_OK(ov_core_create(&core));
EXPECT_NE(nullptr, core);

char* optimization_capabilites = NULL;
ov_core_get_property(core, device_name.c_str(), "OPTIMIZATION_CAPABILITIES", &optimization_capabilites);
if (std::string(optimization_capabilites).find("EXPORT_IMPORT") == std::string::npos) {
GTEST_SKIP() << "Skip this test, cause no EXPORT_IMPORT supported";
}

const char* key = ov_property_key_cache_encryption_callbacks;
ov_encryption_callbacks encryption_callbacks{codec_xor, codec_xor};

ov_compiled_model_t* compiled_model = nullptr;
OV_EXPECT_OK(ov_core_compile_model_from_file(core,
xml_file_name.c_str(),
device_name.c_str(),
2,
&compiled_model,
key,
&encryption_callbacks));
EXPECT_NE(nullptr, compiled_model);

std::string export_path = TestDataHelpers::get_exported_blob_file_name();
OV_EXPECT_OK(ov_compiled_model_export_model(compiled_model, export_path.c_str()));
ov_compiled_model_free(compiled_model);

std::vector<uint8_t> buffer(content_from_file(export_path.c_str(), true));
ov_compiled_model_t* compiled_model_imported = nullptr;
OV_EXPECT_OK(ov_core_import_model(core,
reinterpret_cast<const char*>(buffer.data()),
buffer.size(),
device_name.c_str(),
&compiled_model_imported));
EXPECT_NE(nullptr, compiled_model_imported);
ov_compiled_model_free(compiled_model_imported);
ov_core_free(core);
}

TEST_P(ov_core_test, ov_core_get_versions_by_device_name) {
auto device_name = GetParam();
ov_core_t* core = nullptr;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from openvino._pyopenvino.properties import range_for_async_infer_requests
from openvino._pyopenvino.properties import execution_devices
from openvino._pyopenvino.properties import loaded_from_cache
from openvino._pyopenvino.properties import cache_encryption_callbacks

# Submodules
from openvino.runtime.properties import hint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ void regmodule_properties(py::module m) {
wrap_property_RO(m_properties, ov::execution_devices, "execution_devices");
wrap_property_RO(m_properties, ov::loaded_from_cache, "loaded_from_cache");

wrap_property_WO(m_properties, ov::cache_encryption_callbacks, "cache_encryption_callbacks");

// Submodule hint
py::module m_hint =
m_properties.def_submodule("hint", "openvino.properties.hint submodule that simulates ov::hint");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,11 @@ void wrap_property_RW(py::module m, ov::Property<T, ov::PropertyMutability::RW>
});
}

template <typename T>
void wrap_property_WO(py::module m, ov::Property<T, ov::PropertyMutability::WO> property, std::string func_name) {
m.def(func_name.c_str(), [property](T value) {
return property(value);
});
}

void regmodule_properties(py::module m);
26 changes: 25 additions & 1 deletion src/bindings/python/src/pyopenvino/utils/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,31 @@ py::object from_ov_any(const ov::Any& any) {
std::map<std::string, ov::Any> properties_to_any_map(const std::map<std::string, py::object>& properties) {
std::map<std::string, ov::Any> properties_to_cpp;
for (const auto& property : properties) {
properties_to_cpp[property.first] = Common::utils::py_object_to_any(property.second);
if (property.first == ov::cache_encryption_callbacks.name()) {
auto property_value = property.second;
if (!py::isinstance<py::list>(property_value)) {
OPENVINO_THROW("The value type of ov::cache_encryption_callbacks property is expected list");
}
std::function<std::string(const std::string&)> encrypt_func =
[property_value](const std::string& in_str) -> std::string {
// Acquire GIL, execute Python function
py::gil_scoped_acquire acquire;
auto _list = property_value.cast<py::list>();
return _list[0](in_str).cast<std::string>();
};

std::function<std::string(const std::string&)> decrypt_func =
[property_value](const std::string& in_str) -> std::string {
// Acquire GIL, execute Python function
py::gil_scoped_acquire acquire;
auto _list = property_value.cast<py::list>();
return _list[1](in_str).cast<std::string>();
};
ov::EncryptionCallbacks encryption_callbacks{encrypt_func, decrypt_func};
properties_to_cpp[property.first] = encryption_callbacks;
} else {
properties_to_cpp[property.first] = Common::utils::py_object_to_any(property.second);
}
}
return properties_to_cpp;
}
Expand Down
28 changes: 28 additions & 0 deletions src/bindings/python/tests/test_runtime/test_compiled_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
generate_image,
generate_model_and_image,
generate_relu_compiled_model,
generate_relu_compiled_model_with_config,
encrypt_base64,
decrypt_base64,
create_filename_for_test)
from openvino import Model, Shape, Core, Tensor, serialize
from openvino.runtime import ConstOutput
Expand Down Expand Up @@ -54,6 +57,31 @@ def test_export_import(device):
assert np.argmax(res[new_compiled.outputs[0]]) == 531


@pytest.mark.skipif(
condition=sys.version_info >= (3, 12),
reason="Fails on any Linux platform with Python 3.12. Ticket CVS-133903",
)
def test_export_import_with_encryption(device):
core = Core()

if props.device.Capability.EXPORT_IMPORT not in core.get_property(device, props.device.capabilities):
pytest.skip(f"{core.get_property(device, props.device.full_name)} plugin due-to export, import model API isn't implemented.")

config = {}
config["CACHE_ENCRYPTION_CALLBACKS"] = [encrypt_base64, decrypt_base64]

compiled_model = generate_relu_compiled_model_with_config(device, config)

user_stream = compiled_model.export_model()

new_compiled = core.import_model(user_stream, device, config)

img = generate_image()
res = new_compiled.infer_new_request({"data": img})

assert np.argmax(res[new_compiled.outputs[0]]) == 531


def test_export_import_advanced(device):
import io

Expand Down
22 changes: 22 additions & 0 deletions src/bindings/python/tests/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import sys
import numpy as np
import base64

from sys import platform
from pathlib import Path
Expand Down Expand Up @@ -196,6 +197,27 @@ def generate_relu_compiled_model(
return core.compile_model(model, device, {})


def encrypt_base64(src):
return base64.b64encode(bytes(src, "utf-8"))


def decrypt_base64(src):
return base64.b64decode(bytes(src, "utf-8"))


def generate_relu_compiled_model_with_config(
device,
config,
input_shape: List[int] = None,
input_dtype=np.float32,
) -> openvino.CompiledModel:
if input_shape is None:
input_shape = [1, 3, 32, 32]
model = get_relu_model(input_shape, input_dtype)
core = Core()
return core.compile_model(model, device, config)


def generate_model_and_image(device, input_shape: List[int] = None):
if input_shape is None:
input_shape = [1, 3, 32, 32]
Expand Down
16 changes: 16 additions & 0 deletions src/inference/include/openvino/runtime/properties.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,22 @@ inline std::istream& operator>>(std::istream& is, CacheMode& mode) {
*/
static constexpr Property<CacheMode, PropertyMutability::RW> cache_mode{"CACHE_MODE"};

struct EncryptionCallbacks {
std::function<std::string(const std::string&)> encrypt;
std::function<std::string(const std::string&)> decrypt;
};

/**
* @brief Write-only property to set encryption/decryption function for saving/loading model cache.
* If cache_encryption_callbacks is set, the model cache will be encrypted/decrypted when saving/loading cache.
* cache_encryption_callbacks is enabled in core.compile_model only.
* - First value of the struct is encryption function.
* - Second value of the struct is decryption function.
* @ingroup ov_runtime_cpp_prop_api
*/
static constexpr Property<EncryptionCallbacks, PropertyMutability::WO> cache_encryption_callbacks{
"CACHE_ENCRYPTION_CALLBACKS"};

/**
* @brief Read-only property to provide information about a range for streams on platforms where streams are supported.
* @ingroup ov_runtime_cpp_prop_api
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/hetero/src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Configuration::Configuration(const ov::AnyMap& config, const Configuration& defa
}
}
modelDistributionPolicy = value.as<std::set<ov::hint::ModelDistributionPolicy>>();
} else if (ov::cache_encryption_callbacks == key) {
encryption_callbacks = value.as<EncryptionCallbacks>();
} else {
if (throwOnUnsupported)
OPENVINO_THROW("Property was not found: ", key);
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/hetero/src/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ struct Configuration {

std::set<ov::hint::ModelDistributionPolicy> modelDistributionPolicy = {};

EncryptionCallbacks encryption_callbacks;

ov::AnyMap device_properties;
};
} // namespace hetero
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/intel_cpu/src/compiled_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ ov::Any CompiledModel::get_property(const std::string& name) const {
}

void CompiledModel::export_model(std::ostream& modelStream) const {
ModelSerializer serializer(modelStream);
ModelSerializer serializer(modelStream, m_cfg.cacheEncrypt);
serializer << m_model;
}

Expand Down
8 changes: 8 additions & 0 deletions src/plugins/intel_cpu/src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,14 @@ void Config::readProperties(const ov::AnyMap& prop, const ModelType modelType) {
ov::hint::kv_cache_precision.name(),
". Supported values: u8, bf16, f16, f32");
}
} else if (key == ov::cache_encryption_callbacks.name()) {
try {
auto encryption_callbacks = val.as<EncryptionCallbacks>();
cacheEncrypt = encryption_callbacks.encrypt;
cacheDecrypt = encryption_callbacks.decrypt;
} catch (ov::Exception&) {
OPENVINO_THROW("Wrong value for property key ", ov::cache_encryption_callbacks.name());
}
} else {
OPENVINO_THROW("NotFound: Unsupported property ", key, " by CPU plugin.");
}
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/intel_cpu/src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ struct Config {

int modelPreferThreads = -1;
ModelType modelType = ModelType::Unknown;
std::function<std::string(const std::string&)> cacheEncrypt;
std::function<std::string(const std::string&)> cacheDecrypt;

#ifdef CPU_DEBUG_CAPS
DebugCapsConfig debugCaps;
Expand Down
Loading

0 comments on commit 4d716ba

Please sign in to comment.