Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions .github/workflows/azure.yml

This file was deleted.

28 changes: 21 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ env_logger = "0.11.8"
futures = "^0.3"
futures-util = "0.3"
hex = "0.4.3"
hkdf = "0.12.4"
hyper = { version = "1.6.0" }
hyper-util = { version = "0.1" }
indicatif = "^0.17"
Expand Down Expand Up @@ -81,3 +82,4 @@ uuid = { version = "^1.3" }
walkdir = "2"
warp = "^0.3"
x509-parser = { version = "0.16.0" }
zeroize = "1.8.2"
3 changes: 3 additions & 0 deletions accless/libs/abe4/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ crate-type=["rlib", "staticlib"]
path = "src/lib.rs"

[dependencies]
aes-gcm = { workspace = true, features = ["aes"] }
anyhow.workspace = true
ark-bls12-381.workspace = true
ark-ec.workspace = true
ark-ff.workspace = true
ark-std = { workspace = true, features = ["std"] }
ark-serialize = { workspace = true, features = ["derive"] }
base64.workspace = true
hkdf.workspace = true
log.workspace = true
rand = { workspace = true, features = ["std", "std_rng"] }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
sha2.workspace = true
zeroize.workspace = true
49 changes: 49 additions & 0 deletions accless/libs/abe4/cpp-bindings/abe4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,55 @@ std::optional<std::string> decrypt(const std::string &usk,
return gt_b64;
}

namespace hybrid {

EncryptOutput encrypt(const std::string &mpk, const std::string &policy,
const std::vector<uint8_t> &plaintext,
const std::vector<uint8_t> &aad) {
std::string plaintext_b64 = accless::base64::encode(plaintext);
std::string aad_b64 = accless::base64::encode(aad);

char *result = encrypt_hybrid_abe4(mpk.c_str(), policy.c_str(),
plaintext_b64.c_str(), aad_b64.c_str());
if (!result) {
std::cerr
<< "accless(abe4): FFI call to encrypt_hybrid_abe4 failed. See "
"Rust logs for details."
<< std::endl;
throw std::runtime_error(
"accless(abe4): encrypt_hybrid_abe4 FFI call failed");
}

auto result_json = nlohmann::json::parse(result);
free_string(result);

return {result_json["abe_ct"], result_json["sym_ct"]};
}

std::optional<std::vector<uint8_t>>
decrypt(const std::string &usk, const std::string &gid,
const std::string &policy, const std::string &abe_ct,
const std::string &sym_ct, const std::vector<uint8_t> &aad) {
std::string aad_b64 = accless::base64::encode(aad);
char *result =
decrypt_hybrid_abe4(usk.c_str(), gid.c_str(), policy.c_str(),
abe_ct.c_str(), sym_ct.c_str(), aad_b64.c_str());
if (!result) {
std::cerr
<< "accless(abe4): FFI call to decrypt_hybrid_abe4 failed. See "
"Rust logs for details."
<< std::endl;
return std::nullopt;
}

std::string plaintext_b64(result);
free_string(result);

return accless::base64::decode(plaintext_b64);
}

} // namespace hybrid

std::map<std::string, std::vector<uint8_t>>
unpackFullKey(const std::vector<uint8_t> &full_key_bytes) {
std::map<std::string, std::vector<uint8_t>> result;
Expand Down
49 changes: 49 additions & 0 deletions accless/libs/abe4/cpp-bindings/abe4.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ char *keygen_partial_abe4(const char *gid_cstr,
const char *partial_msk_b64_cstr,
const char *user_attrs_json);
char *policy_authorities_abe4(const char *policy_str);
char *encrypt_hybrid_abe4(const char *mpk_b64, const char *policy_str,
const char *plaintext_b64, const char *aad_b64);
char *decrypt_hybrid_abe4(const char *usk_b64, const char *gid,
const char *policy_str, const char *abe_ct_b64,
const char *sym_ct_b64, const char *aad_b64);

} // extern "C"

Expand Down Expand Up @@ -208,4 +213,48 @@ std::string packFullKey(const std::vector<std::string> &authorities,
* @return A vector with all the attributes that appear in the policy.
*/
std::vector<std::string> getPolicyAuthorities(const std::string &policy);

namespace hybrid {
struct EncryptOutput {
std::string abe_ciphertext;
std::string sym_ciphertext;
};

/**
* @brief Encrypts plaintext and associated data using the hybrid CP-ABE scheme.
*
* This wrapper handles base64 encoding of the plaintext/AAD, calls the Rust
* FFI, and returns base64-encoded ciphertext components.
*
* @param mpk Base64-encoded master public key.
* @param policy Policy string.
* @param plaintext Plaintext bytes to encrypt.
* @param aad Associated data bound to the symmetric encryption.
* @return EncryptOutput containing the base64-encoded ABE and symmetric
* ciphertexts.
* @throws std::runtime_error on error.
*/
EncryptOutput encrypt(const std::string &mpk, const std::string &policy,
const std::vector<uint8_t> &plaintext,
const std::vector<uint8_t> &aad);

/**
* @brief Decrypts a hybrid ciphertext using the provided keys and AAD.
*
* This wrapper calls the Rust FFI and base64-decodes the recovered plaintext.
*
* @param usk Base64-encoded user secret key.
* @param gid Group identifier.
* @param policy Policy string.
* @param abe_ct Base64-encoded ABE ciphertext.
* @param sym_ct Base64-encoded symmetric ciphertext.
* @param aad Associated data bound to the symmetric encryption.
* @return Optional plaintext bytes on success, or std::nullopt on failure.
*/
std::optional<std::vector<uint8_t>>
decrypt(const std::string &usk, const std::string &gid,
const std::string &policy, const std::string &abe_ct,
const std::string &sym_ct, const std::vector<uint8_t> &aad);
} // namespace hybrid

} // namespace accless::abe4
92 changes: 92 additions & 0 deletions accless/libs/abe4/cpp-bindings/api_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ class Abe4ApiTest : public ::testing::Test {

ASSERT_FALSE(decrypted_gt.has_value());
}

void assert_hybrid_round_trip(
const std::vector<accless::abe4::UserAttribute> &user_attrs,
const std::string &policy, const std::string &plaintext,
const std::string &aad) {
auto auths = gather_authorities(user_attrs, policy);
accless::abe4::SetupOutput setup_output = accless::abe4::setup(auths);
std::string gid = "test_gid";
std::string usk_b64 =
accless::abe4::keygen(gid, setup_output.msk, user_attrs);

std::vector<uint8_t> plaintext_bytes(plaintext.begin(),
plaintext.end());
std::vector<uint8_t> aad_bytes(aad.begin(), aad.end());

auto hybrid_ct = accless::abe4::hybrid::encrypt(
setup_output.mpk, policy, plaintext_bytes, aad_bytes);
auto decrypted = accless::abe4::hybrid::decrypt(
usk_b64, gid, policy, hybrid_ct.abe_ciphertext,
hybrid_ct.sym_ciphertext, aad_bytes);

ASSERT_TRUE(decrypted.has_value());
std::string decrypted_str(decrypted->begin(), decrypted->end());
EXPECT_EQ(plaintext, decrypted_str);
}
};

TEST_F(Abe4ApiTest, SingleAuthSingleOk) {
Expand Down Expand Up @@ -190,3 +215,70 @@ TEST_F(Abe4ApiTest, SimpleNegationOk) {
std::string policy = "!A.c:2";
assert_decryption_ok(user_attrs, policy);
}

TEST_F(Abe4ApiTest, HybridRoundTripOk) {
std::vector<accless::abe4::UserAttribute> user_attrs = {
{"A", "a", "0"},
{"A", "c", "1"},
};
std::string policy = "A.a:0 & !A.c:0";
std::string plaintext = "hybrid plaintext payload";
std::string aad = "hybrid aad data";
assert_hybrid_round_trip(user_attrs, policy, plaintext, aad);
}

TEST_F(Abe4ApiTest, HybridDecryptFailsForUnauthorizedUser) {
std::vector<accless::abe4::UserAttribute> user_attrs = {};
std::string policy = "A.a:0";
std::string plaintext = "hybrid plaintext payload";
std::string aad = "hybrid aad data";

auto auths = gather_authorities(user_attrs, policy);
accless::abe4::SetupOutput setup_output = accless::abe4::setup(auths);
std::string gid = "test_gid";
std::string usk_b64 =
accless::abe4::keygen(gid, setup_output.msk, user_attrs);

std::vector<uint8_t> plaintext_bytes(plaintext.begin(), plaintext.end());
std::vector<uint8_t> aad_bytes(aad.begin(), aad.end());
auto hybrid_ct = accless::abe4::hybrid::encrypt(setup_output.mpk, policy,
plaintext_bytes, aad_bytes);

auto decrypted = accless::abe4::hybrid::decrypt(
usk_b64, gid, policy, hybrid_ct.abe_ciphertext,
hybrid_ct.sym_ciphertext, aad_bytes);
ASSERT_FALSE(decrypted.has_value());
}

TEST_F(Abe4ApiTest, HybridRejectsModifiedAad) {
std::vector<accless::abe4::UserAttribute> user_attrs = {{"A", "a", "0"}};
std::string policy = "A.a:0";
std::string plaintext = "hybrid plaintext payload";
std::string aad = "hybrid aad data";
std::string wrong_aad = "tampered aad";

auto auths = gather_authorities(user_attrs, policy);
accless::abe4::SetupOutput setup_output = accless::abe4::setup(auths);
std::string gid = "test_gid";
std::string usk_b64 =
accless::abe4::keygen(gid, setup_output.msk, user_attrs);

std::vector<uint8_t> plaintext_bytes(plaintext.begin(), plaintext.end());
std::vector<uint8_t> aad_bytes(aad.begin(), aad.end());
std::vector<uint8_t> wrong_aad_bytes(wrong_aad.begin(), wrong_aad.end());

auto hybrid_ct = accless::abe4::hybrid::encrypt(setup_output.mpk, policy,
plaintext_bytes, aad_bytes);

auto decrypted = accless::abe4::hybrid::decrypt(
usk_b64, gid, policy, hybrid_ct.abe_ciphertext,
hybrid_ct.sym_ciphertext, aad_bytes);
ASSERT_TRUE(decrypted.has_value());
std::string decrypted_str(decrypted->begin(), decrypted->end());
EXPECT_EQ(plaintext, decrypted_str);

auto tampered = accless::abe4::hybrid::decrypt(
usk_b64, gid, policy, hybrid_ct.abe_ciphertext,
hybrid_ct.sym_ciphertext, wrong_aad_bytes);
ASSERT_FALSE(tampered.has_value());
}
Loading
Loading