Skip to content

Commit 09ecd42

Browse files
authored
Merge pull request #69 from faasm/feature-cpabe-hybrid
[accless] Implement CP-ABE Hybrid Scheme
2 parents 82492ad + 1478bf5 commit 09ecd42

File tree

28 files changed

+2306
-35
lines changed

28 files changed

+2306
-35
lines changed

.github/workflows/azure.yml

Lines changed: 0 additions & 23 deletions
This file was deleted.

Cargo.lock

Lines changed: 21 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ env_logger = "0.11.8"
4646
futures = "^0.3"
4747
futures-util = "0.3"
4848
hex = "0.4.3"
49+
hkdf = "0.12.4"
4950
hyper = { version = "1.6.0" }
5051
hyper-util = { version = "0.1" }
5152
indicatif = "^0.17"
@@ -81,3 +82,4 @@ uuid = { version = "^1.3" }
8182
walkdir = "2"
8283
warp = "^0.3"
8384
x509-parser = { version = "0.16.0" }
85+
zeroize = "1.8.2"

accless/libs/abe4/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ crate-type=["rlib", "staticlib"]
1414
path = "src/lib.rs"
1515

1616
[dependencies]
17+
aes-gcm = { workspace = true, features = ["aes"] }
1718
anyhow.workspace = true
1819
ark-bls12-381.workspace = true
1920
ark-ec.workspace = true
2021
ark-ff.workspace = true
2122
ark-std = { workspace = true, features = ["std"] }
2223
ark-serialize = { workspace = true, features = ["derive"] }
2324
base64.workspace = true
25+
hkdf.workspace = true
2426
log.workspace = true
2527
rand = { workspace = true, features = ["std", "std_rng"] }
2628
serde = { workspace = true, features = ["derive"] }
2729
serde_json.workspace = true
2830
sha2.workspace = true
31+
zeroize.workspace = true

accless/libs/abe4/cpp-bindings/abe4.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,55 @@ std::optional<std::string> decrypt(const std::string &usk,
127127
return gt_b64;
128128
}
129129

130+
namespace hybrid {
131+
132+
EncryptOutput encrypt(const std::string &mpk, const std::string &policy,
133+
const std::vector<uint8_t> &plaintext,
134+
const std::vector<uint8_t> &aad) {
135+
std::string plaintext_b64 = accless::base64::encode(plaintext);
136+
std::string aad_b64 = accless::base64::encode(aad);
137+
138+
char *result = encrypt_hybrid_abe4(mpk.c_str(), policy.c_str(),
139+
plaintext_b64.c_str(), aad_b64.c_str());
140+
if (!result) {
141+
std::cerr
142+
<< "accless(abe4): FFI call to encrypt_hybrid_abe4 failed. See "
143+
"Rust logs for details."
144+
<< std::endl;
145+
throw std::runtime_error(
146+
"accless(abe4): encrypt_hybrid_abe4 FFI call failed");
147+
}
148+
149+
auto result_json = nlohmann::json::parse(result);
150+
free_string(result);
151+
152+
return {result_json["abe_ct"], result_json["sym_ct"]};
153+
}
154+
155+
std::optional<std::vector<uint8_t>>
156+
decrypt(const std::string &usk, const std::string &gid,
157+
const std::string &policy, const std::string &abe_ct,
158+
const std::string &sym_ct, const std::vector<uint8_t> &aad) {
159+
std::string aad_b64 = accless::base64::encode(aad);
160+
char *result =
161+
decrypt_hybrid_abe4(usk.c_str(), gid.c_str(), policy.c_str(),
162+
abe_ct.c_str(), sym_ct.c_str(), aad_b64.c_str());
163+
if (!result) {
164+
std::cerr
165+
<< "accless(abe4): FFI call to decrypt_hybrid_abe4 failed. See "
166+
"Rust logs for details."
167+
<< std::endl;
168+
return std::nullopt;
169+
}
170+
171+
std::string plaintext_b64(result);
172+
free_string(result);
173+
174+
return accless::base64::decode(plaintext_b64);
175+
}
176+
177+
} // namespace hybrid
178+
130179
std::map<std::string, std::vector<uint8_t>>
131180
unpackFullKey(const std::vector<uint8_t> &full_key_bytes) {
132181
std::map<std::string, std::vector<uint8_t>> result;

accless/libs/abe4/cpp-bindings/abe4.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ char *keygen_partial_abe4(const char *gid_cstr,
1919
const char *partial_msk_b64_cstr,
2020
const char *user_attrs_json);
2121
char *policy_authorities_abe4(const char *policy_str);
22+
char *encrypt_hybrid_abe4(const char *mpk_b64, const char *policy_str,
23+
const char *plaintext_b64, const char *aad_b64);
24+
char *decrypt_hybrid_abe4(const char *usk_b64, const char *gid,
25+
const char *policy_str, const char *abe_ct_b64,
26+
const char *sym_ct_b64, const char *aad_b64);
2227

2328
} // extern "C"
2429

@@ -208,4 +213,48 @@ std::string packFullKey(const std::vector<std::string> &authorities,
208213
* @return A vector with all the attributes that appear in the policy.
209214
*/
210215
std::vector<std::string> getPolicyAuthorities(const std::string &policy);
216+
217+
namespace hybrid {
218+
struct EncryptOutput {
219+
std::string abe_ciphertext;
220+
std::string sym_ciphertext;
221+
};
222+
223+
/**
224+
* @brief Encrypts plaintext and associated data using the hybrid CP-ABE scheme.
225+
*
226+
* This wrapper handles base64 encoding of the plaintext/AAD, calls the Rust
227+
* FFI, and returns base64-encoded ciphertext components.
228+
*
229+
* @param mpk Base64-encoded master public key.
230+
* @param policy Policy string.
231+
* @param plaintext Plaintext bytes to encrypt.
232+
* @param aad Associated data bound to the symmetric encryption.
233+
* @return EncryptOutput containing the base64-encoded ABE and symmetric
234+
* ciphertexts.
235+
* @throws std::runtime_error on error.
236+
*/
237+
EncryptOutput encrypt(const std::string &mpk, const std::string &policy,
238+
const std::vector<uint8_t> &plaintext,
239+
const std::vector<uint8_t> &aad);
240+
241+
/**
242+
* @brief Decrypts a hybrid ciphertext using the provided keys and AAD.
243+
*
244+
* This wrapper calls the Rust FFI and base64-decodes the recovered plaintext.
245+
*
246+
* @param usk Base64-encoded user secret key.
247+
* @param gid Group identifier.
248+
* @param policy Policy string.
249+
* @param abe_ct Base64-encoded ABE ciphertext.
250+
* @param sym_ct Base64-encoded symmetric ciphertext.
251+
* @param aad Associated data bound to the symmetric encryption.
252+
* @return Optional plaintext bytes on success, or std::nullopt on failure.
253+
*/
254+
std::optional<std::vector<uint8_t>>
255+
decrypt(const std::string &usk, const std::string &gid,
256+
const std::string &policy, const std::string &abe_ct,
257+
const std::string &sym_ct, const std::vector<uint8_t> &aad);
258+
} // namespace hybrid
259+
211260
} // namespace accless::abe4

accless/libs/abe4/cpp-bindings/api_tests.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,31 @@ class Abe4ApiTest : public ::testing::Test {
5454

5555
ASSERT_FALSE(decrypted_gt.has_value());
5656
}
57+
58+
void assert_hybrid_round_trip(
59+
const std::vector<accless::abe4::UserAttribute> &user_attrs,
60+
const std::string &policy, const std::string &plaintext,
61+
const std::string &aad) {
62+
auto auths = gather_authorities(user_attrs, policy);
63+
accless::abe4::SetupOutput setup_output = accless::abe4::setup(auths);
64+
std::string gid = "test_gid";
65+
std::string usk_b64 =
66+
accless::abe4::keygen(gid, setup_output.msk, user_attrs);
67+
68+
std::vector<uint8_t> plaintext_bytes(plaintext.begin(),
69+
plaintext.end());
70+
std::vector<uint8_t> aad_bytes(aad.begin(), aad.end());
71+
72+
auto hybrid_ct = accless::abe4::hybrid::encrypt(
73+
setup_output.mpk, policy, plaintext_bytes, aad_bytes);
74+
auto decrypted = accless::abe4::hybrid::decrypt(
75+
usk_b64, gid, policy, hybrid_ct.abe_ciphertext,
76+
hybrid_ct.sym_ciphertext, aad_bytes);
77+
78+
ASSERT_TRUE(decrypted.has_value());
79+
std::string decrypted_str(decrypted->begin(), decrypted->end());
80+
EXPECT_EQ(plaintext, decrypted_str);
81+
}
5782
};
5883

5984
TEST_F(Abe4ApiTest, SingleAuthSingleOk) {
@@ -190,3 +215,70 @@ TEST_F(Abe4ApiTest, SimpleNegationOk) {
190215
std::string policy = "!A.c:2";
191216
assert_decryption_ok(user_attrs, policy);
192217
}
218+
219+
TEST_F(Abe4ApiTest, HybridRoundTripOk) {
220+
std::vector<accless::abe4::UserAttribute> user_attrs = {
221+
{"A", "a", "0"},
222+
{"A", "c", "1"},
223+
};
224+
std::string policy = "A.a:0 & !A.c:0";
225+
std::string plaintext = "hybrid plaintext payload";
226+
std::string aad = "hybrid aad data";
227+
assert_hybrid_round_trip(user_attrs, policy, plaintext, aad);
228+
}
229+
230+
TEST_F(Abe4ApiTest, HybridDecryptFailsForUnauthorizedUser) {
231+
std::vector<accless::abe4::UserAttribute> user_attrs = {};
232+
std::string policy = "A.a:0";
233+
std::string plaintext = "hybrid plaintext payload";
234+
std::string aad = "hybrid aad data";
235+
236+
auto auths = gather_authorities(user_attrs, policy);
237+
accless::abe4::SetupOutput setup_output = accless::abe4::setup(auths);
238+
std::string gid = "test_gid";
239+
std::string usk_b64 =
240+
accless::abe4::keygen(gid, setup_output.msk, user_attrs);
241+
242+
std::vector<uint8_t> plaintext_bytes(plaintext.begin(), plaintext.end());
243+
std::vector<uint8_t> aad_bytes(aad.begin(), aad.end());
244+
auto hybrid_ct = accless::abe4::hybrid::encrypt(setup_output.mpk, policy,
245+
plaintext_bytes, aad_bytes);
246+
247+
auto decrypted = accless::abe4::hybrid::decrypt(
248+
usk_b64, gid, policy, hybrid_ct.abe_ciphertext,
249+
hybrid_ct.sym_ciphertext, aad_bytes);
250+
ASSERT_FALSE(decrypted.has_value());
251+
}
252+
253+
TEST_F(Abe4ApiTest, HybridRejectsModifiedAad) {
254+
std::vector<accless::abe4::UserAttribute> user_attrs = {{"A", "a", "0"}};
255+
std::string policy = "A.a:0";
256+
std::string plaintext = "hybrid plaintext payload";
257+
std::string aad = "hybrid aad data";
258+
std::string wrong_aad = "tampered aad";
259+
260+
auto auths = gather_authorities(user_attrs, policy);
261+
accless::abe4::SetupOutput setup_output = accless::abe4::setup(auths);
262+
std::string gid = "test_gid";
263+
std::string usk_b64 =
264+
accless::abe4::keygen(gid, setup_output.msk, user_attrs);
265+
266+
std::vector<uint8_t> plaintext_bytes(plaintext.begin(), plaintext.end());
267+
std::vector<uint8_t> aad_bytes(aad.begin(), aad.end());
268+
std::vector<uint8_t> wrong_aad_bytes(wrong_aad.begin(), wrong_aad.end());
269+
270+
auto hybrid_ct = accless::abe4::hybrid::encrypt(setup_output.mpk, policy,
271+
plaintext_bytes, aad_bytes);
272+
273+
auto decrypted = accless::abe4::hybrid::decrypt(
274+
usk_b64, gid, policy, hybrid_ct.abe_ciphertext,
275+
hybrid_ct.sym_ciphertext, aad_bytes);
276+
ASSERT_TRUE(decrypted.has_value());
277+
std::string decrypted_str(decrypted->begin(), decrypted->end());
278+
EXPECT_EQ(plaintext, decrypted_str);
279+
280+
auto tampered = accless::abe4::hybrid::decrypt(
281+
usk_b64, gid, policy, hybrid_ct.abe_ciphertext,
282+
hybrid_ct.sym_ciphertext, wrong_aad_bytes);
283+
ASSERT_FALSE(tampered.has_value());
284+
}

0 commit comments

Comments
 (0)