Skip to content

Commit

Permalink
fix: fix "ecdh_cose_encrypted_key"
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Aug 30, 2024
1 parent 95091ac commit a5f4660
Show file tree
Hide file tree
Showing 13 changed files with 263 additions and 62 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ strip = true
opt-level = 's'

[workspace.package]
version = "0.3.0"
version = "0.3.1"
edition = "2021"
repository = "https://github.com/ldclabs/ic-cose"
keywords = ["config", "cbor", "canister", "icp", "encryption"]
Expand Down
22 changes: 14 additions & 8 deletions src/ic_cose_canister/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,31 @@
## Candid API

```shell
admin_add_allowed_apis : (vec text) -> (Result);
admin_add_auditors : (vec principal) -> (Result);
admin_add_managers : (vec principal) -> (Result);
admin_create_namespace : (CreateNamespaceInput) -> (Result_1);
admin_list_namespace : (opt text, opt nat32) -> (Result_2) query;
admin_remove_allowed_apis : (vec text) -> (Result);
admin_remove_auditors : (vec principal) -> (Result);
admin_remove_managers : (vec principal) -> (Result);
ecdh_encrypted_cose_key : (CosePath, ECDHInput) -> (Result_3);
ecdh_setting_get : (SettingPath, ECDHInput) -> (Result_4);
ecdsa_public_key : (opt PublicKeyInput) -> (Result_5) query;
ecdsa_sign : (SignInput) -> (Result_6);
ecdsa_sign_identity : (SignIdentityInput) -> (Result_6);
ecdh_cose_encrypted_key : (SettingPath, ECDHInput) -> (Result_3);
ecdsa_public_key : (opt PublicKeyInput) -> (Result_4) query;
ecdsa_sign : (SignInput) -> (Result_5);
namespace_add_auditors : (text, vec principal) -> (Result);
namespace_add_managers : (text, vec principal) -> (Result);
namespace_add_users : (text, vec principal) -> (Result);
namespace_get_info : (text) -> (Result_1) query;
namespace_remove_auditors : (text, vec principal) -> (Result);
namespace_remove_managers : (text, vec principal) -> (Result);
namespace_remove_users : (text, vec principal) -> (Result);
namespace_top_up : (text, nat) -> (Result_6);
namespace_update_info : (UpdateNamespaceInput) -> (Result);
schnorr_public_key : (SchnorrAlgorithm, opt PublicKeyInput) -> (
Result_5,
Result_4,
) query;
schnorr_sign : (SchnorrAlgorithm, SignInput) -> (Result_6);
schnorr_sign_identity : (SchnorrAlgorithm, SignIdentityInput) -> (Result_6);
schnorr_sign : (SchnorrAlgorithm, SignInput) -> (Result_5);
schnorr_sign_identity : (SchnorrAlgorithm, SignIdentityInput) -> (Result_5);
setting_add_readers : (SettingPath, vec principal) -> (Result);
setting_create : (SettingPath, CreateSettingInput) -> (Result_7);
setting_get : (SettingPath) -> (Result_8) query;
Expand All @@ -46,10 +47,14 @@
Result_7,
);
state_get_info : () -> (Result_10) query;
validate_admin_add_allowed_apis : (vec text) -> (Result);
validate_admin_add_auditors : (vec principal) -> (Result);
validate_admin_add_managers : (vec principal) -> (Result);
validate_admin_remove_allowed_apis : (vec text) -> (Result);
validate_admin_remove_auditors : (vec principal) -> (Result);
validate_admin_remove_managers : (vec principal) -> (Result);
vetkd_encrypted_key : (SettingPath, blob) -> (Result_5);
vetkd_public_key : (SettingPath) -> (Result_5);
```

The complete Candid API definition can be found in the [ic_cose_canister.did](https://github.com/ldclabs/ic-cose/tree/main/src/ic_cose_canister/ic_cose_canister.did) file.
Expand All @@ -67,6 +72,7 @@ dfx deploy ic_cose_canister --argument "(opt variant {Init =
ecdsa_key_name = \"dfx_test_key\";
schnorr_key_name = \"dfx_test_key\";
vetkd_key_name = \"test_key_1\";
allowed_apis = vec {};
subnet_size = 0;
freezing_threshold = 1_000_000_000_000;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ic_cose_canister/src/api_cose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ async fn ecdh_cose_encrypted_key(
})?;

let aad = spk.2.as_slice();
let kek = store::ns::inner_schnorr_kek(&spk, &key_id).await?;
let kek = store::ns::inner_derive_kek(&spk, &key_id)?;
let kek = cose_aes256_key(kek, key_id.into_vec());
let kek = kek.to_vec().map_err(format_error)?;

Expand All @@ -118,7 +118,7 @@ async fn ecdh_cose_encrypted_key(
let (shared_secret, public_key) = ecdh_x25519(secret_key, *ecdh.public_key);
let key = cose_encrypt0(&kek, shared_secret.as_bytes(), aad, *ecdh.nonce, None)?;
Ok(ECDHOutput {
payload: key,
payload: key.into(),
public_key: public_key.to_bytes().into(),
})
}
Expand Down
45 changes: 24 additions & 21 deletions src/ic_cose_canister/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use ic_stable_structures::{
DefaultMemoryImpl, StableBTreeMap, StableCell, Storable,
};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
use serde_bytes::{ByteArray, ByteBuf};
use std::{
borrow::Cow,
cell::RefCell,
Expand Down Expand Up @@ -60,6 +60,8 @@ pub struct State {
pub subnet_size: u64,
#[serde(rename = "f")]
pub freezing_threshold: u64, // freezing writing threshold in cycles
#[serde(default, rename = "iv")]
pub init_vector: ByteArray<32>, // should not be exposed
}

impl State {
Expand Down Expand Up @@ -432,10 +434,13 @@ pub mod state {
})
.ok();

let iv: [u8; 32] = rand_bytes().await.expect("failed to generate IV");

with_mut(|r| {
r.ecdsa_public_key = ecdsa_public_key;
r.schnorr_ed25519_public_key = schnorr_ed25519_public_key;
r.schnorr_secp256k1_public_key = schnorr_secp256k1_public_key;
r.init_vector = iv.into();
});
}

Expand Down Expand Up @@ -669,26 +674,24 @@ pub mod ns {
Ok(ByteBuf::from(token))
}

pub async fn inner_schnorr_kek(
spk: &SettingPathKey,
key_id: &[u8],
) -> Result<[u8; 32], String> {
let key_name = state::with(|r| r.schnorr_key_name.clone());
let derivation_path = vec![
b"COSE_Symmetric_Key".to_vec(),
spk.2.to_bytes().to_vec(),
vec![spk.1],
spk.0.to_bytes().to_vec(),
];
let message = mac3_256(spk.0.as_bytes(), key_id);
let sig = sign_with_schnorr(
key_name,
SchnorrAlgorithm::Ed25519,
derivation_path,
message.into(),
)
.await?;
Ok(mac3_256(spk.0.as_bytes(), &sig))
pub fn inner_derive_kek(spk: &SettingPathKey, key_id: &[u8]) -> Result<[u8; 32], String> {
state::with(|s| {
let pk = s
.schnorr_secp256k1_public_key
.as_ref()
.ok_or("no schnorr secp256k1 public key")?;

let derivation_path = vec![
b"COSE_Symmetric_Key".to_vec(),
s.init_vector.to_vec(),
spk.2.to_bytes().to_vec(),
vec![spk.1],
spk.0.to_bytes().to_vec(),
];
let pk =
derive_schnorr_public_key(SchnorrAlgorithm::Bip340Secp256k1, pk, derivation_path)?;
Ok(mac3_256(&pk.public_key, key_id))
})
}

pub async fn inner_vetkd_public_key(spk: &SettingPathKey) -> Result<Vec<u8>, String> {
Expand Down
24 changes: 23 additions & 1 deletion src/ic_cose_types/src/cose/cwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use coset::cwt::*;
const CLOCK_SKEW: i64 = 5 * 60; // 5 minutes
pub static SCOPE_NAME: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Scope);

pub fn cwt_from_cwt(data: &[u8], now_sec: i64) -> Result<ClaimsSet, String> {
pub fn cwt_from(data: &[u8], now_sec: i64) -> Result<ClaimsSet, String> {
let claims = ClaimsSet::from_slice(data).map_err(|err| format!("invalid claims: {}", err))?;
if let Some(ref exp) = claims.expiration_time {
let exp = match exp {
Expand Down Expand Up @@ -39,3 +39,25 @@ pub fn get_scope(claims: &ClaimsSet) -> Result<String, String> {
let scope = scope.1.as_text().ok_or("invalid scope text")?;
Ok(scope.to_string())
}

#[cfg(test)]
mod test {
use super::*;
use const_hex::decode;

#[test]
fn cwt_works() {
let data = decode("a801781b35336379672d79796161612d61616161702d61687075612d63616902783f693267616d2d75756533792d75787779642d6d7a7968622d6e697268642d687a336c342d32687733662d34667a76772d6c707676632d64716472672d3771650366746573746572041a66d11526051a66d10716061a66d10716075029420f3d16231d2de11fb7c33bbe971e096d4e616d6573706163652e2a3a5f").unwrap();
let claims = cwt_from(&data, 1724974880).unwrap();
assert_eq!(
claims.issuer,
Some("53cyg-yyaaa-aaaap-ahpua-cai".to_string())
);
assert_eq!(
claims.subject,
Some("i2gam-uue3y-uxwyd-mzyhb-nirhd-hz3l4-2hw3f-4fzvw-lpvvc-dqdrg-7qe".to_string())
);
assert_eq!(claims.audience, Some("tester".to_string()));
assert_eq!(get_scope(&claims).unwrap(), "Namespace.*:_");
}
}
78 changes: 78 additions & 0 deletions src/ic_cose_types/src/cose/ecdh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,81 @@ pub fn ecdh_x25519(secret: [u8; 32], their_public: [u8; 32]) -> (SharedSecret, P
public,
)
}

#[cfg(test)]
mod test {
use candid::Principal;
use const_hex::{decode, encode};

use super::*;
use crate::cose::{encrypt0::cose_decrypt0, get_cose_key_secret, CborSerializable, CoseKey};

#[test]
fn ecdh_works() {
let subject =
Principal::from_text("i2gam-uue3y-uxwyd-mzyhb-nirhd-hz3l4-2hw3f-4fzvw-lpvvc-dqdrg-7qe")
.unwrap();
let aad = subject.as_slice();

// ecdh_cose_encrypted_key request 1:
let secret: [u8; 32] =
decode("65775d6e01b640d83a042466b06c0e77796f5367e243cc51e3b741f4bd3aa227")
.unwrap()
.try_into()
.unwrap();
let xpub = PublicKey::from(&StaticSecret::from(secret));
println!("xpub: {:?}", encode(xpub.as_bytes()));
// 6233976850d2fc6ab653306b332dde4389a4e87b79d521a331683cf90102c478

// dfx canister call ic_cose_canister ecdh_cose_encrypted_key '(record {
// ns = "_";
// key = blob "\01\02\03\04";
// subject = null;
// version = 0;
// user_owned = true;
// }, record { public_key = blob "\62\33\97\68\50\d2\fc\6a\b6\53\30\6b\33\2d\de\43\89\a4\e8\7b\79\d5\21\a3\31\68\3c\f9\01\02\c4\78"; nonce = blob "\72\ca\b0\8a\fb\f3\32\eb\2b\b1\da\8d"})' --ic
let their_public: [u8; 32] =
decode("d391bdd98bb760fa0f0ca4c04711fb6fff3160e243483f9613b9c572c0398074")
.unwrap()
.try_into()
.unwrap();
let (shared_secret, _) = ecdh_x25519(secret, their_public);
let payload = decode("d08343a10103a1054c72cab08afbf332eb2bb1da8d583e4aa106b6803554e97b5027b2d7c53c61ce130620088d79d0839a6a8b61424ecd2c69f2ad26a91ee31bcb1b65d32130cc3e585f741a7c0e91c1f1f03172a8").unwrap();
let cose_key = cose_decrypt0(&payload, shared_secret.as_bytes(), aad).unwrap();
println!("cose_key: {:?}", encode(&cose_key));
let cose_key = CoseKey::from_slice(&cose_key).unwrap();
let kek = get_cose_key_secret(cose_key).unwrap();
println!("kek: {:?}", encode(&kek));

// ecdh_cose_encrypted_key request 2:
let secret: [u8; 32] =
decode("65775d6e01b640d83a042466b06c0e77796f5367e243cc51e3b741f4bd3aa228")
.unwrap()
.try_into()
.unwrap();
let xpub = PublicKey::from(&StaticSecret::from(secret));
println!("xpub: {:?}", encode(xpub.as_bytes()));
// d50defd7d7cc946b828aaa4db70fe6f99f259f8a58802290b9b21f32f417fb4d

// dfx canister call ic_cose_canister ecdh_cose_encrypted_key '(record {
// ns = "_";
// key = blob "\01\02\03\04";
// subject = null;
// version = 0;
// user_owned = true;
// }, record { public_key = blob "\d5\0d\ef\d7\d7\cc\94\6b\82\8a\aa\4d\b7\0f\e6\f9\9f\25\9f\8a\58\80\22\90\b9\b2\1f\32\f4\17\fb\4d"; nonce = blob "\72\ca\b0\8a\fb\f3\32\eb\2b\b1\da\88"})' --ic
let their_public: [u8; 32] =
decode("dfca9cf65bb0b0a3e4197098cfb5891efab9d8ed135b49dea80f1aeeab557263")
.unwrap()
.try_into()
.unwrap();
let (shared_secret, _) = ecdh_x25519(secret, their_public);
let payload = decode("d08343a10103a1054c72cab08afbf332eb2bb1da88583e74b2537cd28f81be717e209acb81323b070cfbc4d44df08a525bdc17698e70b84e665d7fa98d1aa168570d3fd2d7dd3eadcc390107102969e2e613eb6a87").unwrap();
let cose_key = cose_decrypt0(&payload, shared_secret.as_bytes(), aad).unwrap();
println!("cose_key: {:?}", encode(&cose_key));
let cose_key = CoseKey::from_slice(&cose_key).unwrap();
let kek2 = get_cose_key_secret(cose_key).unwrap();
println!("kek2: {:?}", encode(&kek2));
assert_eq!(kek, kek2);
}
}
33 changes: 33 additions & 0 deletions src/ic_cose_types/src/cose/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,36 @@ pub fn ed25519_verify_any(
false => Err("ed25519 signature verification failed".to_string()),
}
}

#[cfg(test)]
mod test {
use const_hex::decode;

use super::*;

#[test]
fn ed25519_verify_works() {
// generated with:
// dfx canister call ic_cose_canister schnorr_public_key '(variant { ed25519 }, opt record {
// ns = "_";
// derivation_path = vec {};
// })' --ic
let pk =
decode("dded78d6f1087ebe259f8dadd83f5bce72cbd5d95aa93fe237bb6f53b05fe809").unwrap();
let pk: [u8; 32] = pk.try_into().unwrap();

// generated with:
// dfx canister call ic_cose_canister schnorr_sign '(variant { ed25519 }, record {
// ns = "_";
// derivation_path = vec {};
// message = blob "\62\33\97\68\50\d2\fc\6a\b6\53\30\6b\33\2d\de\43\89\a4\e8\7b\79\d5\21\a3\31\68\3c\f9\01\02\c4\78";
// })' --ic
let message =
decode("6233976850d2fc6ab653306b332dde4389a4e87b79d521a331683cf90102c478").unwrap();
let signature = decode("aba0f24e4c025e136adc6928b2ea736d1621c3b307f9283756240180a0b9dd0a504cc70b79f3c44c5c894c3105281e73035fe551f3c9ef964beb8548b3e63b03").unwrap();
assert!(ed25519_verify(&pk, &message, &signature).is_ok());

let signature = decode("96ea613d0a26f3812bdee85b262c898393b063b56379d6e9d75e0ab28be820cd4f42fdfb60f8a6fc081393b9407be9387d7f68fe6dec4699dc69b7ace6990303").unwrap();
assert!(ed25519_verify(&pk, &message, &signature).is_ok());
}
}
Loading

0 comments on commit a5f4660

Please sign in to comment.