Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 4 additions & 0 deletions .github/workflows/backend-motoko.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
cd backend/mo/ic_vetkeys
mops install
mops test
cd ../canisters/ic_vetkeys_manager_canister
make test
backend-motoko-tests-darwin:
runs-on: macos-15
steps:
Expand All @@ -45,3 +47,5 @@ jobs:
cd backend/mo/ic_vetkeys
mops install
mops test
cd ../canisters/ic_vetkeys_manager_canister
make test
11 changes: 11 additions & 0 deletions backend/mo/canisters/ic_vetkeys_manager_canister/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
PWD:=$(shell pwd)

.PHONY: compile-wasm
.SILENT: compile-wasm
compile-wasm:
dfx build --check

.PHONY: test
.SILENT: test
test: compile-wasm
CUSTOM_WASM_PATH=$(PWD)/.dfx/local/canisters/ic_vetkeys_manager_canister/ic_vetkeys_manager_canister.wasm cargo test -p ic-vetkeys-manager-canister
Comment thread
fspreiss marked this conversation as resolved.
9 changes: 9 additions & 0 deletions backend/mo/canisters/ic_vetkeys_manager_canister/dfx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"canisters": {
"ic_vetkeys_manager_canister": {
"main": "src/Main.mo",
"type": "motoko",
"args": "--enhanced-orthogonal-persistence"
}
}
}
81 changes: 81 additions & 0 deletions backend/mo/canisters/ic_vetkeys_manager_canister/src/Main.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { KeyManager } "../../../ic_vetkeys/src";
import Types "../../../ic_vetkeys/src/Types";
import Principal "mo:base/Principal";
import Text "mo:base/Text";
import Blob "mo:base/Blob";
import Result "mo:base/Result";

actor {
var keyManager = KeyManager.KeyManager<Types.AccessRights>("key_manager", Types.accessRightsOperations());
public type ByteBuf = { inner : Blob };
Comment thread
fspreiss marked this conversation as resolved.
public type Result<Ok, Err> = {
#Ok : Ok;
#Err : Err;
};
Comment thread
altkdf marked this conversation as resolved.

public query (msg) func get_accessible_shared_key_ids() : async [(Principal, Blob)] {
Comment thread
fspreiss marked this conversation as resolved.
Outdated
keyManager.getAccessibleSharedKeyIds(msg.caller);
};

public query (msg) func get_shared_user_access_for_key(
key_owner : Principal,
key_name : Blob,
Comment thread
fspreiss marked this conversation as resolved.
Outdated
) : async Result<[(Principal, Types.AccessRights)], Text> {
convertResult(keyManager.getSharedUserAccessForKey(msg.caller, (key_owner, key_name)));
};

public shared func get_vetkey_verification_key() : async ByteBuf {
Comment thread
fspreiss marked this conversation as resolved.
let inner = await keyManager.getVetkeyVerificationKey();
{ inner };
};

public shared (msg) func get_encrypted_vetkey(
key_owner : Principal,
key_name : ByteBuf,
transport_key : ByteBuf,
) : async Result<ByteBuf, Text> {
let vetkeyBytebuf = await keyManager.getEncryptedVetkey(msg.caller, (key_owner, key_name.inner), transport_key.inner);
switch (vetkeyBytebuf) {
case (#err(e)) { #Err(e) };
case (#ok(inner)) { #Ok({ inner }) };
};
Comment thread
fspreiss marked this conversation as resolved.
};

public query (msg) func get_user_rights(
key_owner : Principal,
key_name : ByteBuf,
user : Principal,
) : async Result<?Types.AccessRights, Text> {
convertResult(keyManager.getUserRights(msg.caller, (key_owner, key_name.inner), user));
};

public shared (msg) func set_user_rights(
key_owner : Principal,
key_name : ByteBuf,
user : Principal,
access_rights : Types.AccessRights,
) : async Result<?Types.AccessRights, Text> {
convertResult(keyManager.setUserRights(msg.caller, (key_owner, key_name.inner), user, access_rights));
};

public shared (msg) func remove_user(
key_owner : Principal,
key_name : ByteBuf,
user : Principal,
) : async Result<?Types.AccessRights, Text> {
convertResult(keyManager.removeUserRights(msg.caller, (key_owner, key_name.inner), user));
};

// Testing API
public func set_vetkd_testing_canister_id(vetkd_testing_canister : Principal) {
keyManager.setVetKDTestingCanister(Principal.toText(vetkd_testing_canister));
};

/// Convert to the result type compatible with Rust's `Result`
private func convertResult<Ok, Err>(result : Result.Result<Ok, Err>) : Result<Ok, Err> {
switch (result) {
case (#err(e)) { #Err(e) };
case (#ok(o)) { #Ok(o) };
};
};
};
27 changes: 17 additions & 10 deletions backend/mo/ic_vetkeys/src/key_manager/KeyManager.mo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import OrderedMap "mo:base/OrderedMap";
import Result "mo:base/Result";
import Types "../Types";
import Text "mo:base/Text";
import Nat8 "mo:base/Nat8";

module {
public type VetKeyVerificationKey = Blob;
Expand All @@ -20,12 +21,12 @@ module {
vetkd_public_key : ({
canister_id : ?Principal;
derivation_path : [Blob];
key_id : { curve : { #bls12_381_g2 }; name : Text };
key_id : { curve : { #bls12_381 }; name : Text };
}) -> async ({ public_key : Blob });
vetkd_derive_encrypted_key : ({
derivation_path : [Blob];
vetkd_encrypted_key : ({
public_key_derivation_path : [Blob];
derivation_id : Blob;
key_id : { curve : { #bls12_381_g2 }; name : Text };
key_id : { curve : { #bls12_381 }; name : Text };
encryption_public_key : Blob;
}) -> async ({ encrypted_key : Blob });
};
Expand All @@ -51,7 +52,7 @@ module {
public class KeyManager<T>(domainSeparator : Text, accessRightsOperations : Types.AccessControlOperations<T>) {
public var accessControl : OrderedMap.Map<Principal, [(KeyId, T)]> = accessControlMapOps().empty();
public var sharedKeys : OrderedMap.Map<KeyId, [Principal]> = sharedKeysMapOps().empty();
public var managementCanisterPrincipalText = "aaaaa-aa";
var managementCanisterPrincipalText = "aaaaa-aa";
let domainSeparatorBytes = Text.encodeUtf8(domainSeparator);

// Get accessible shared key IDs for a caller
Expand Down Expand Up @@ -115,21 +116,23 @@ module {
switch (ensureUserCanRead(caller, keyId)) {
case (#err(msg)) { #err(msg) };
case (#ok(_)) {
let principalBytes = Blob.toArray(Principal.toBlob(keyId.0));
let derivationId = Array.flatten<Nat8>([
Blob.toArray(Principal.toBlob(keyId.0)),
[Nat8.fromNat(Array.size<Nat8>(principalBytes))],
principalBytes,
Blob.toArray(keyId.1),
]);

let derivationPath = [domainSeparatorBytes];

let request = {
derivation_id = Blob.fromArray(derivationId);
derivation_path = derivationPath;
public_key_derivation_path = derivationPath;
key_id = bls12_381TestKey1();
encryption_public_key = transportKey;
};

let (reply) = await (actor (managementCanisterPrincipalText) : VetkdSystemApi).vetkd_derive_encrypted_key(request);
let (reply) = await (actor (managementCanisterPrincipalText) : VetkdSystemApi).vetkd_encrypted_key(request);
#ok(reply.encrypted_key);
};
};
Expand Down Expand Up @@ -293,6 +296,10 @@ module {
};
};

public func setVetKDTestingCanister(canister : Text) {
managementCanisterPrincipalText := canister;
};

private func ensureUserCanGetUserRights(user : Principal, keyId : KeyId) : Result.Result<T, Text> {
if (Principal.equal(user, keyId.0)) {
return #ok(accessRightsOperations.ownerRights());
Expand Down Expand Up @@ -339,7 +346,7 @@ module {
};

// Helper function for BLS12-381 test key
func bls12_381TestKey1() : { curve : { #bls12_381_g2 }; name : Text } {
{ curve = #bls12_381_g2; name = "insecure_text_key_1" };
func bls12_381TestKey1() : { curve : { #bls12_381 }; name : Text } {
{ curve = #bls12_381; name = "insecure_test_key_1" };
};
};
8 changes: 3 additions & 5 deletions backend/mo/ic_vetkeys/src/lib.mo
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import Types "Types";
module {
public type AccessControlOperations<T> = Types.AccessControlOperations<T>;
public type AccessRights = Types.AccessRights;

public type KeyManager<T> = KeyManagerModule.KeyManager<T>;
public let KeyManager = KeyManagerModule.KeyManager;
public type EncryptedMaps<T> = EncryptedMapsModule.EncryptedMaps<T>;
public let EncryptedMaps = EncryptedMapsModule.EncryptedMaps;
public let accessRightsOperations = Types.accessRightsOperations;

public let KeyManager = KeyManagerModule;
public let EncryptedMaps = EncryptedMapsModule;
};
6 changes: 3 additions & 3 deletions backend/mo/ic_vetkeys/test/EncryptedMaps.test.mo
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import Text "mo:base/Text";
import Blob "mo:base/Blob";
import { test } "mo:test";

type EncryptedMaps = VetKey.EncryptedMaps<VetKey.AccessRights>;
type EncryptedMaps = VetKey.EncryptedMaps.EncryptedMaps<VetKey.AccessRights>;
let accessRightsOperations = VetKey.accessRightsOperations();
func newEncryptedMaps() : EncryptedMaps {
EncryptedMaps<VetKey.AccessRights>("encrypted maps", accessRightsOperations);
EncryptedMaps.EncryptedMaps<VetKey.AccessRights>("encrypted maps", accessRightsOperations);
};

let p1 = Principal.fromText("2vxsx-fae");
Expand All @@ -21,7 +21,7 @@ let mapValue = Text.encodeUtf8("some value");
test(
"can remove map values",
func() {
let encryptedMaps = VetKey.EncryptedMaps<VetKey.AccessRights>("encrypted maps", accessRightsOperations);
let encryptedMaps = newEncryptedMaps();
let result = encryptedMaps.removeMapValues(p1, (p1, mapName));
switch (result) {
case (#ok(keys)) {
Expand Down
5 changes: 2 additions & 3 deletions backend/mo/ic_vetkeys/test/KeyManager.test.mo
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ let p1 = Principal.fromText("2vxsx-fae");
let p2 = Principal.fromText("aaaaa-aa");
let keyName = Text.encodeUtf8("some key");

type KeyManager = VetKey.KeyManager<VetKey.AccessRights>;
func newKeyManager() : KeyManager {
VetKey.KeyManager<VetKey.AccessRights>("key manager", accessRightsOperations);
func newKeyManager() : VetKey.KeyManager.KeyManager<VetKey.AccessRights> {
VetKey.KeyManager.KeyManager<VetKey.AccessRights>("key manager", accessRightsOperations);
};

test(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ fn map_sharing_should_work() {
map_owner,
map_name.clone(),
env.principal_1,
AccessRights::Read,
AccessRights::ReadWriteManage,
))
.unwrap(),
)
Expand All @@ -125,7 +125,7 @@ fn map_sharing_should_work() {
encode_args((map_owner, map_name.clone(), env.principal_1)).unwrap(),
)
.unwrap();
assert_eq!(current_rights_shared, Some(AccessRights::Read));
assert_eq!(current_rights_shared, Some(AccessRights::ReadWriteManage));

let mut get_vetkey = |caller: Principal| -> Vec<u8> {
let transport_key = random_transport_key(rng);
Expand Down
24 changes: 18 additions & 6 deletions backend/rs/canisters/ic_vetkeys_manager_canister/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ fn key_sharing_should_work() {
let not_key_owner = env.principal_1;
let key_name = random_key_name(rng);

assert_eq!(
env.query::<Result<Option<AccessRights>, String>>(
not_key_owner,
"get_user_rights",
encode_args((key_owner, key_name.clone(), not_key_owner)).unwrap(),
),
Err("unauthorized".to_string())
);

let prev_rights = env
.update::<Result<Option<AccessRights>, String>>(
env.principal_0,
Expand All @@ -107,7 +116,7 @@ fn key_sharing_should_work() {
key_owner,
key_name.clone(),
env.principal_1,
AccessRights::Read,
AccessRights::ReadWriteManage,
))
.unwrap(),
)
Expand All @@ -130,7 +139,7 @@ fn key_sharing_should_work() {
encode_args((key_owner, key_name.clone(), not_key_owner)).unwrap(),
)
.unwrap();
assert_eq!(current_rights_shared, Some(AccessRights::Read));
assert_eq!(current_rights_shared, Some(AccessRights::ReadWriteManage));

let mut get_vetkey = |caller: Principal| -> Vec<u8> {
let transport_key = random_transport_key(rng);
Expand Down Expand Up @@ -235,10 +244,13 @@ impl TestEnvironment {
}

fn load_key_manager_example_canister_wasm() -> Vec<u8> {
let wasm_path_string = format!(
"{}/target/wasm32-unknown-unknown/release/ic_vetkeys_manager_canister.wasm",
git_root_dir()
);
let wasm_path_string = match std::env::var("CUSTOM_WASM_PATH") {
Ok(path) if !path.is_empty() => path,
_ => format!(
"{}/target/wasm32-unknown-unknown/release/ic_vetkeys_manager_canister.wasm",
git_root_dir()
),
};
let wasm_path = Path::new(&wasm_path_string);
let wasm_bytes = std::fs::read(wasm_path).expect(
"wasm does not exist - run `cargo build --release --target wasm32-unknown-unknown --features expose-testing-api`",
Expand Down
24 changes: 6 additions & 18 deletions backend/rs/ic_vetkeys/src/encrypted_maps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,7 @@ impl<T: AccessControl> EncryptedMaps<T> {
caller: Principal,
key_id: KeyId,
) -> Result<Vec<MapKey>, String> {
match self.key_manager.get_user_rights(caller, key_id, caller)? {
Some(a) if a.can_write() => Ok(()),
Some(_) | None => Err("unauthorized".to_string()),
}?;
self.key_manager.ensure_user_can_write(caller, key_id)?;

let keys: Vec<_> = self
.mapkey_vals
Expand All @@ -117,7 +114,7 @@ impl<T: AccessControl> EncryptedMaps<T> {
caller: Principal,
key_id: KeyId,
) -> Result<Vec<(MapKey, EncryptedMapValue)>, String> {
self.key_manager.get_user_rights(caller, key_id, caller)?;
self.key_manager.ensure_user_can_read(caller, key_id)?;

Ok(self
.mapkey_vals
Expand All @@ -134,9 +131,8 @@ impl<T: AccessControl> EncryptedMaps<T> {
key_id: KeyId,
key: MapKey,
) -> Result<Option<EncryptedMapValue>, String> {
self.key_manager
.get_user_rights(caller, key_id, caller)
.map(|_| self.mapkey_vals.get(&(key_id, key)))
self.key_manager.ensure_user_can_read(caller, key_id)?;
Ok(self.mapkey_vals.get(&(key_id, key)))
}

/// Retrieves the non-empty map names owned by the caller.
Expand Down Expand Up @@ -205,11 +201,7 @@ impl<T: AccessControl> EncryptedMaps<T> {
key: MapKey,
encrypted_value: EncryptedMapValue,
) -> Result<Option<EncryptedMapValue>, String> {
match self.key_manager.get_user_rights(caller, key_id, caller)? {
Some(a) if a.can_write() => Ok(()),
Some(_) | None => Err("unauthorized".to_string()),
}?;

self.key_manager.ensure_user_can_write(caller, key_id)?;
Ok(self.mapkey_vals.insert((key_id, key), encrypted_value))
}

Expand All @@ -220,11 +212,7 @@ impl<T: AccessControl> EncryptedMaps<T> {
key_id: KeyId,
key: MapKey,
) -> Result<Option<EncryptedMapValue>, String> {
match self.key_manager.get_user_rights(caller, key_id, caller)? {
Some(a) if a.can_write() => Ok(()),
Some(_) | None => Err("unauthorized".to_string()),
}?;

self.key_manager.ensure_user_can_write(caller, key_id)?;
Ok(self.mapkey_vals.remove(&(key_id, key)))
}

Expand Down
Loading