diff --git a/Cargo.lock b/Cargo.lock index b59946a..85bc229 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,7 +1204,7 @@ checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" [[package]] name = "ic_cose_canister" -version = "0.3.0" +version = "0.3.1" dependencies = [ "candid", "ciborium", @@ -1222,7 +1222,7 @@ dependencies = [ [[package]] name = "ic_cose_types" -version = "0.3.0" +version = "0.3.1" dependencies = [ "aes-gcm", "candid", diff --git a/Cargo.toml b/Cargo.toml index dfc5b5a..6c837e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/src/ic_cose_canister/README.md b/src/ic_cose_canister/README.md index 811073b..cffdb59 100644 --- a/src/ic_cose_canister/README.md +++ b/src/ic_cose_canister/README.md @@ -11,17 +11,17 @@ ## 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); @@ -29,12 +29,13 @@ 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; @@ -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. @@ -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; } diff --git a/src/ic_cose_canister/src/api_cose.rs b/src/ic_cose_canister/src/api_cose.rs index db6249d..775fd81 100644 --- a/src/ic_cose_canister/src/api_cose.rs +++ b/src/ic_cose_canister/src/api_cose.rs @@ -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)?; @@ -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(), }) } diff --git a/src/ic_cose_canister/src/store.rs b/src/ic_cose_canister/src/store.rs index c9ed63b..da1f411 100644 --- a/src/ic_cose_canister/src/store.rs +++ b/src/ic_cose_canister/src/store.rs @@ -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, @@ -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 { @@ -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(); }); } @@ -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, String> { diff --git a/src/ic_cose_types/src/cose/cwt.rs b/src/ic_cose_types/src/cose/cwt.rs index 3be14d6..1226964 100644 --- a/src/ic_cose_types/src/cose/cwt.rs +++ b/src/ic_cose_types/src/cose/cwt.rs @@ -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 { +pub fn cwt_from(data: &[u8], now_sec: i64) -> Result { 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 { @@ -39,3 +39,25 @@ pub fn get_scope(claims: &ClaimsSet) -> Result { 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.*:_"); + } +} diff --git a/src/ic_cose_types/src/cose/ecdh.rs b/src/ic_cose_types/src/cose/ecdh.rs index f2afb72..9fc27e9 100644 --- a/src/ic_cose_types/src/cose/ecdh.rs +++ b/src/ic_cose_types/src/cose/ecdh.rs @@ -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); + } +} diff --git a/src/ic_cose_types/src/cose/ed25519.rs b/src/ic_cose_types/src/cose/ed25519.rs index fdf75b8..950872a 100644 --- a/src/ic_cose_types/src/cose/ed25519.rs +++ b/src/ic_cose_types/src/cose/ed25519.rs @@ -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()); + } +} diff --git a/src/ic_cose_types/src/cose/encrypt0.rs b/src/ic_cose_types/src/cose/encrypt0.rs index 6d2a9fc..8c55d9d 100644 --- a/src/ic_cose_types/src/cose/encrypt0.rs +++ b/src/ic_cose_types/src/cose/encrypt0.rs @@ -2,7 +2,6 @@ use coset::{ iana, CborSerializable, CoseEncrypt0, CoseEncrypt0Builder, HeaderBuilder, TaggedCborSerializable, }; -use serde_bytes::ByteBuf; use super::{ aes::{aes256_gcm_decrypt, aes256_gcm_encrypt}, @@ -19,7 +18,7 @@ pub fn cose_encrypt0( aad: &[u8], nonce: [u8; 12], key_id: Option>, -) -> Result { +) -> Result, String> { let protected = HeaderBuilder::new() .algorithm(iana::Algorithm::A256GCM) .build(); @@ -35,15 +34,14 @@ pub fn cose_encrypt0( aes256_gcm_encrypt(secret, &nonce, enc, plain_data).unwrap() }) .build(); - let payload = e0.to_tagged_vec().map_err(format_error)?; - Ok(ByteBuf::from(payload)) + e0.to_tagged_vec().map_err(format_error) } pub fn cose_decrypt0( payload: &[u8], // COSE_Encrypt0 item secret: &[u8; 32], aad: &[u8], -) -> Result { +) -> Result, String> { let e0 = CoseEncrypt0::from_slice(skip_prefix(&ENCRYPT0_TAG, payload)).map_err(format_error)?; let nonce = e0.unprotected.iv.first_chunk::<12>().ok_or_else(|| { format!( @@ -51,21 +49,19 @@ pub fn cose_decrypt0( e0.unprotected.iv.len() ) })?; - let plain_data = e0.decrypt(aad, |cipher_data, enc| { + e0.decrypt(aad, |cipher_data, enc| { aes256_gcm_decrypt(secret, nonce, enc, cipher_data) - })?; - Ok(ByteBuf::from(plain_data)) + }) } -pub fn decrypt(item: CoseEncrypt0, secret: &[u8; 32], aad: &[u8]) -> Result { +pub fn decrypt(item: CoseEncrypt0, secret: &[u8; 32], aad: &[u8]) -> Result, String> { let nonce = item.unprotected.iv.first_chunk::<12>().ok_or_else(|| { format!( "invalid nonce length, expected 12, got {}", item.unprotected.iv.len() ) })?; - let plain_data = item.decrypt(aad, |cipher_data, enc| { + item.decrypt(aad, |cipher_data, enc| { aes256_gcm_decrypt(secret, nonce, enc, cipher_data) - })?; - Ok(ByteBuf::from(plain_data)) + }) } diff --git a/src/ic_cose_types/src/cose/k256.rs b/src/ic_cose_types/src/cose/k256.rs index 90eab60..a7efed9 100644 --- a/src/ic_cose_types/src/cose/k256.rs +++ b/src/ic_cose_types/src/cose/k256.rs @@ -1,15 +1,21 @@ -use super::{format_error, sha256}; +use super::format_error; use k256::ecdsa::signature::hazmat::PrehashVerifier; // use k256::schnorr::signature::Verifier; pub use k256::{ecdsa, schnorr}; -pub fn secp256k1_verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<(), String> { +pub fn secp256k1_verify( + public_key: &[u8], + message_hash: &[u8], + signature: &[u8], +) -> Result<(), String> { + if message_hash.len() != 32 { + return Err("message_hash must be 32 bytes".to_string()); + } let key = ecdsa::VerifyingKey::from_sec1_bytes(public_key).map_err(format_error)?; let sig = ecdsa::Signature::try_from(signature).map_err(format_error)?; - let digest = sha256(message); - match key.verify_prehash(&digest, &sig).is_ok() { + match key.verify_prehash(message_hash, &sig).is_ok() { true => Ok(()), false => Err("secp256k1 signature verification failed".to_string()), } @@ -17,14 +23,17 @@ pub fn secp256k1_verify(public_key: &[u8], message: &[u8], signature: &[u8]) -> pub fn secp256k1_verify_any( public_keys: &[ecdsa::VerifyingKey], - message: &[u8], + message_hash: &[u8], signature: &[u8], ) -> Result<(), String> { + if message_hash.len() != 32 { + return Err("message_hash must be 32 bytes".to_string()); + } + let sig = ecdsa::Signature::try_from(signature).map_err(format_error)?; - let digest = sha256(message); match public_keys .iter() - .any(|key| key.verify_prehash(&digest, &sig).is_ok()) + .any(|key| key.verify_prehash(message_hash, &sig).is_ok()) { true => Ok(()), false => Err("secp256k1 signature verification failed".to_string()), @@ -47,3 +56,35 @@ pub fn secp256k1_verify_any( // false => Err("schnorr secp256k1 signature verification failed".to_string()), // } // } + +#[cfg(test)] +mod test { + use const_hex::decode; + + use super::*; + + #[test] + fn secp256k1_verify_works() { + // generated with: + // dfx canister call ic_cose_canister ecdsa_public_key '(opt record { + // ns = "_"; + // derivation_path = vec {}; + // })' --ic + let pk = + decode("025ddf616f61959973238149361b788ec6bb8e01d896c80a6b82538cf27f61b934").unwrap(); + + // generated with: + // dfx canister call ic_cose_canister ecdsa_sign '(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("f17f8cb96a9e8845a3fd9a33fee76d9c54c1949b16ca23537d4f6f75a07ecdd355dd7ac662b9ae7a2d779ea6cb1ad399240f450024eef46d6e6ab1493fe1eb95").unwrap(); + assert!(secp256k1_verify(&pk, &message, &signature).is_ok()); + + let signature = decode("6d8983dbeaf2977d2a41d69e0a6fb46b51fb7c1616a8ddd8bb948e1b08bb10e31eee92ef0f8b44ff62f231e6afd7f443a132414d431b57a6ce6dd23ffac8f878").unwrap(); + assert!(secp256k1_verify(&pk, &message, &signature).is_ok()); + } +} diff --git a/src/ic_cose_types/src/cose/mod.rs b/src/ic_cose_types/src/cose/mod.rs index 2cb3de0..fe60b2c 100644 --- a/src/ic_cose_types/src/cose/mod.rs +++ b/src/ic_cose_types/src/cose/mod.rs @@ -65,7 +65,7 @@ pub fn cose_aes256_key(secret: [u8; 32], key_id: Vec) -> CoseKey { .build() } -pub fn get_cose_key_secret(key: CoseKey) -> Result<[u8; 32], String> { +pub fn get_cose_key_secret(key: CoseKey) -> Result, String> { let key_label = match key.kty { RegisteredLabel::Assigned(iana::KeyType::Symmetric) => { Label::Int(iana::SymmetricKeyParameter::K as i64) @@ -83,12 +83,9 @@ pub fn get_cose_key_secret(key: CoseKey) -> Result<[u8; 32], String> { for (label, value) in key.params { if label == key_label { - let val: [u8; 32] = value + return value .into_bytes() - .map_err(|_| "invalid secret key".to_string())? - .try_into() - .map_err(|_| "invalid secret key".to_string())?; - return Ok(val); + .map_err(|_| "invalid secret key".to_string()); } } Err("missing secret key".to_string()) diff --git a/src/ic_cose_types/src/cose/sign1.rs b/src/ic_cose_types/src/cose/sign1.rs index 349f325..317aaba 100644 --- a/src/ic_cose_types/src/cose/sign1.rs +++ b/src/ic_cose_types/src/cose/sign1.rs @@ -45,3 +45,28 @@ pub fn cose_sign1_from( } Ok(cs1) } + +#[cfg(test)] +mod test { + use super::*; + use candid::Principal; + use const_hex::decode; + + #[test] + fn cose_sign1_from_works() { + // root public key + let pk = + decode("8fbb003d3f662fa0ea23b27681f53ef46cd5ba4ce887f569e9c60342cc766642").unwrap(); + let pk: [u8; 32] = pk.try_into().unwrap(); + let pk = ed25519::VerifyingKey::from_bytes(&pk).unwrap(); + let subject = + Principal::from_text("i2gam-uue3y-uxwyd-mzyhb-nirhd-hz3l4-2hw3f-4fzvw-lpvvc-dqdrg-7qe") + .unwrap(); + // from schnorr_sign_identity API + let data = decode("8443a10127a0589ca801781b35336379672d79796161612d61616161702d61687075612d63616902783f693267616d2d75756533792d75787779642d6d7a7968622d6e697268642d687a336c342d32687733662d34667a76772d6c707676632d64716472672d3771650366746573746572041a66d11526051a66d10716061a66d10716075029420f3d16231d2de11fb7c33bbe971e096d4e616d6573706163652e2a3a5f5840bc6f9f4305a19a4a3952388cb8667e340ead39878d1ada1b671fe9b81f1c2db1c479508e5c9c20e17f5168a0587f5c049047317f4bb5c8b8f2c84e05fce6c806").unwrap(); + let res = cose_sign1_from(&data, subject.as_slice(), &[], &[pk]).unwrap(); + println!("{:?}", res); + + assert_eq!(res.payload, Some(decode("a801781b35336379672d79796161612d61616161702d61687075612d63616902783f693267616d2d75756533792d75787779642d6d7a7968622d6e697268642d687a336c342d32687733662d34667a76772d6c707676632d64716472672d3771650366746573746572041a66d11526051a66d10716061a66d10716075029420f3d16231d2de11fb7c33bbe971e096d4e616d6573706163652e2a3a5f").unwrap())); + } +} diff --git a/src/ic_cose_types/src/lib.rs b/src/ic_cose_types/src/lib.rs index 0656df6..09b7e3c 100644 --- a/src/ic_cose_types/src/lib.rs +++ b/src/ic_cose_types/src/lib.rs @@ -27,7 +27,7 @@ pub fn to_cbor_bytes(obj: &impl Serialize) -> Vec { /// * `s` - A string slice that holds the name to be validated. /// /// # Returns -/// * `Ok(())` if the name only contains valid characters (a-z, 0-9, '-', '_'). +/// * `Ok(())` if the name only contains valid characters (a-z, 0-9, '_'). /// * `Err(String)` if the name is empty or contains invalid characters. /// pub fn validate_key(s: &str) -> Result<(), String> { @@ -40,7 +40,7 @@ pub fn validate_key(s: &str) -> Result<(), String> { } for c in s.chars() { - if !matches!(c, 'a'..='z' | '0'..='9' | '-'| '_' ) { + if !matches!(c, 'a'..='z' | '0'..='9' | '_' ) { return Err(format!("invalid character: {}", c)); } }