diff --git a/veilid-core/src/crypto/crypto_system.rs b/veilid-core/src/crypto/crypto_system.rs index f0bae8de..61e140b6 100644 --- a/veilid-core/src/crypto/crypto_system.rs +++ b/veilid-core/src/crypto/crypto_system.rs @@ -46,7 +46,7 @@ pub trait CryptoSystem { // Authentication fn sign(&self, key: &PublicKey, secret: &SecretKey, data: &[u8]) -> VeilidAPIResult; - fn verify(&self, key: &PublicKey, data: &[u8], signature: &Signature) -> VeilidAPIResult<()>; + fn verify(&self, key: &PublicKey, data: &[u8], signature: &Signature) -> VeilidAPIResult; // AEAD Encrypt/Decrypt fn aead_overhead(&self) -> usize; diff --git a/veilid-core/src/crypto/envelope.rs b/veilid-core/src/crypto/envelope.rs index dc92e317..d371e182 100644 --- a/veilid-core/src/crypto/envelope.rs +++ b/veilid-core/src/crypto/envelope.rs @@ -172,9 +172,12 @@ impl Envelope { ); // Validate signature - vcrypto + if !vcrypto .verify(&sender_id, &data[0..(data.len() - 64)], &signature) - .map_err(VeilidAPIError::internal)?; + .map_err(VeilidAPIError::internal)? + { + apibail_parse_error!("signature verification of envelope failed", signature); + } // Return envelope Ok(Self { diff --git a/veilid-core/src/crypto/mod.rs b/veilid-core/src/crypto/mod.rs index 8be06b81..3ed636e4 100644 --- a/veilid-core/src/crypto/mod.rs +++ b/veilid-core/src/crypto/mod.rs @@ -238,27 +238,28 @@ impl Crypto { } /// Signature set verification - /// Returns the set of signature cryptokinds that validate and are supported - /// If any cryptokinds are supported and do not validate, the whole operation - /// returns an error + /// Returns Some() the set of signature cryptokinds that validate and are supported + /// Returns None if any cryptokinds are supported and do not validate pub fn verify_signatures( &self, - node_ids: &[TypedKey], + public_keys: &[TypedKey], data: &[u8], typed_signatures: &[TypedSignature], - ) -> VeilidAPIResult { - let mut out = TypedKeyGroup::with_capacity(node_ids.len()); + ) -> VeilidAPIResult> { + let mut out = TypedKeyGroup::with_capacity(public_keys.len()); for sig in typed_signatures { - for nid in node_ids { + for nid in public_keys { if nid.kind == sig.kind { if let Some(vcrypto) = self.get(sig.kind) { - vcrypto.verify(&nid.value, data, &sig.value)?; + if !vcrypto.verify(&nid.value, data, &sig.value)? { + return Ok(None); + } out.add(*nid); } } } } - Ok(out) + Ok(Some(out)) } /// Signature set generation diff --git a/veilid-core/src/crypto/none/mod.rs b/veilid-core/src/crypto/none/mod.rs index 48b80106..8f4e1642 100644 --- a/veilid-core/src/crypto/none/mod.rs +++ b/veilid-core/src/crypto/none/mod.rs @@ -143,13 +143,13 @@ impl CryptoSystem for CryptoSystemNONE { // Validation fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool { let data = vec![0u8; 512]; - let sig = match self.sign(dht_key, dht_key_secret, &data) { - Ok(s) => s, - Err(_) => { - return false; - } + let Ok(sig) = self.sign(dht_key, dht_key_secret, &data) else { + return false; }; - self.verify(dht_key, &data, &sig).is_ok() + let Ok(v) = self.verify(dht_key, &data, &sig) else { + return false; + }; + v } fn validate_hash(&self, data: &[u8], dht_key: &PublicKey) -> bool { let bytes = *blake3::hash(data).as_bytes(); @@ -205,7 +205,7 @@ impl CryptoSystem for CryptoSystemNONE { dht_key: &PublicKey, data: &[u8], signature: &Signature, - ) -> VeilidAPIResult<()> { + ) -> VeilidAPIResult { let mut dig = Blake3Digest512::new(); dig.update(data); let sig = dig.finalize(); @@ -217,19 +217,13 @@ impl CryptoSystem for CryptoSystemNONE { .copy_from_slice(&do_xor_32(&in_sig_bytes[32..64], &signature.bytes[32..64])); if !is_bytes_eq_32(&verify_bytes[0..32], 0u8) { - return Err(VeilidAPIError::parse_error( - "Verification failed", - "signature 0..32 is invalid", - )); + return Ok(false); } if !is_bytes_eq_32(&do_xor_32(&verify_bytes[32..64], &dht_key.bytes), 0xFFu8) { - return Err(VeilidAPIError::parse_error( - "Verification failed", - "signature 32..64 is invalid", - )); + return Ok(false); } - Ok(()) + return Ok(true); } // AEAD Encrypt/Decrypt diff --git a/veilid-core/src/crypto/receipt.rs b/veilid-core/src/crypto/receipt.rs index 56bd4c90..a2922172 100644 --- a/veilid-core/src/crypto/receipt.rs +++ b/veilid-core/src/crypto/receipt.rs @@ -129,9 +129,12 @@ impl Receipt { ); // Validate signature - vcrypto + if !vcrypto .verify(&sender_id, &data[0..(data.len() - 64)], &signature) - .map_err(VeilidAPIError::generic)?; + .map_err(VeilidAPIError::generic)? + { + apibail_parse_error!("signature failure in receipt", signature); + } // Get nonce let nonce: Nonce = Nonce::new( diff --git a/veilid-core/src/crypto/tests/test_types.rs b/veilid-core/src/crypto/tests/test_types.rs index 6d08d34b..2636d916 100644 --- a/veilid-core/src/crypto/tests/test_types.rs +++ b/veilid-core/src/crypto/tests/test_types.rs @@ -64,49 +64,55 @@ pub async fn test_sign_and_verify(vcrypto: CryptoSystemVersion) { assert_eq!( vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &a1), - Ok(()) + Ok(true) ); assert_eq!( vcrypto.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &a2), - Ok(()) + Ok(true) + ); + assert_eq!( + vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &a2), + Ok(false) + ); + assert_eq!( + vcrypto.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &a1), + Ok(false) ); - assert!(vcrypto - .verify(&dht_key, LOREM_IPSUM.as_bytes(), &a2) - .is_err()); - assert!(vcrypto - .verify(&dht_key2, LOREM_IPSUM.as_bytes(), &a1) - .is_err()); // Try verifications that should work assert_eq!( vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig), - Ok(()) + Ok(true) ); assert_eq!( vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig_b), - Ok(()) + Ok(true) ); assert_eq!( vcrypto.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig2), - Ok(()) + Ok(true) ); assert_eq!( vcrypto.verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig_c), - Ok(()) + Ok(true) ); // Try verifications that shouldn't work - assert!(vcrypto - .verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig) - .is_err()); - assert!(vcrypto - .verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig2) - .is_err()); - assert!(vcrypto - .verify(&dht_key2, CHEEZBURGER.as_bytes(), &dht_sig_c) - .is_err()); - assert!(vcrypto - .verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig) - .is_err()); + assert_eq!( + vcrypto.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig), + Ok(false) + ); + assert_eq!( + vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig2), + Ok(false) + ); + assert_eq!( + vcrypto.verify(&dht_key2, CHEEZBURGER.as_bytes(), &dht_sig_c), + Ok(false) + ); + assert_eq!( + vcrypto.verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig), + Ok(false) + ); } pub async fn test_key_conversions(vcrypto: CryptoSystemVersion) { diff --git a/veilid-core/src/crypto/vld0/mod.rs b/veilid-core/src/crypto/vld0/mod.rs index b2b2ce1c..a8c6382d 100644 --- a/veilid-core/src/crypto/vld0/mod.rs +++ b/veilid-core/src/crypto/vld0/mod.rs @@ -161,13 +161,13 @@ impl CryptoSystem for CryptoSystemVLD0 { // Validation fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool { let data = vec![0u8; 512]; - let sig = match self.sign(dht_key, dht_key_secret, &data) { - Ok(s) => s, - Err(_) => { - return false; - } + let Ok(sig) = self.sign(dht_key, dht_key_secret, &data) else { + return false; }; - self.verify(dht_key, &data, &sig).is_ok() + let Ok(v) = self.verify(dht_key, &data, &sig) else { + return false; + }; + v } fn validate_hash(&self, data: &[u8], dht_key: &PublicKey) -> bool { let bytes = *blake3::hash(data).as_bytes(); @@ -219,7 +219,9 @@ impl CryptoSystem for CryptoSystemVLD0 { let sig = Signature::new(sig_bytes.to_bytes()); - self.verify(dht_key, data, &sig)?; + if !self.verify(dht_key, data, &sig)? { + apibail_internal!("newly created signature does not verify"); + } Ok(sig) } @@ -228,7 +230,7 @@ impl CryptoSystem for CryptoSystemVLD0 { dht_key: &PublicKey, data: &[u8], signature: &Signature, - ) -> VeilidAPIResult<()> { + ) -> VeilidAPIResult { let pk = ed::VerifyingKey::from_bytes(&dht_key.bytes) .map_err(|e| VeilidAPIError::parse_error("Public key is invalid", e))?; let sig = ed::Signature::from_bytes(&signature.bytes); @@ -236,9 +238,13 @@ impl CryptoSystem for CryptoSystemVLD0 { let mut dig: ed::Sha512 = ed::Sha512::default(); dig.update(data); - pk.verify_prehashed_strict(dig, Some(VEILID_DOMAIN_SIGN), &sig) - .map_err(|e| VeilidAPIError::parse_error("Verification failed", e))?; - Ok(()) + if pk + .verify_prehashed_strict(dig, Some(VEILID_DOMAIN_SIGN), &sig) + .is_err() + { + return Ok(false); + } + Ok(true) } // AEAD Encrypt/Decrypt diff --git a/veilid-core/src/routing_table/route_spec_store/mod.rs b/veilid-core/src/routing_table/route_spec_store/mod.rs index 82270aef..e51421ef 100644 --- a/veilid-core/src/routing_table/route_spec_store/mod.rs +++ b/veilid-core/src/routing_table/route_spec_store/mod.rs @@ -694,9 +694,16 @@ impl RouteSpecStore { } } else { // Verify a signature for a hop node along the route - if let Err(e) = vcrypto.verify(hop_public_key, data, &signatures[hop_n]) { - log_rpc!(debug "failed to verify signature for hop {} at {} on private route {}: {}", hop_n, hop_public_key, public_key, e); - return None; + match vcrypto.verify(hop_public_key, data, &signatures[hop_n]) { + Ok(true) => {} + Ok(false) => { + log_rpc!(debug "invalid signature for hop {} at {} on private route {}", hop_n, hop_public_key, public_key); + return None; + } + Err(e) => { + log_rpc!(debug "errir verifying signature for hop {} at {} on private route {}: {}", hop_n, hop_public_key, public_key, e); + return None; + } } } } diff --git a/veilid-core/src/routing_table/types/signed_direct_node_info.rs b/veilid-core/src/routing_table/types/signed_direct_node_info.rs index ddc8198b..1cb69b1f 100644 --- a/veilid-core/src/routing_table/types/signed_direct_node_info.rs +++ b/veilid-core/src/routing_table/types/signed_direct_node_info.rs @@ -27,8 +27,11 @@ impl SignedDirectNodeInfo { let node_info_bytes = Self::make_signature_bytes(&self.node_info, self.timestamp)?; // Verify the signatures that we can - let validated_node_ids = + let opt_validated_node_ids = crypto.verify_signatures(node_ids, &node_info_bytes, &self.signatures)?; + let Some(validated_node_ids) = opt_validated_node_ids else { + apibail_generic!("verification error in direct node info"); + }; if validated_node_ids.is_empty() { apibail_generic!("no valid node ids in direct node info"); } diff --git a/veilid-core/src/routing_table/types/signed_relayed_node_info.rs b/veilid-core/src/routing_table/types/signed_relayed_node_info.rs index bed33ba0..13861bd4 100644 --- a/veilid-core/src/routing_table/types/signed_relayed_node_info.rs +++ b/veilid-core/src/routing_table/types/signed_relayed_node_info.rs @@ -53,8 +53,11 @@ impl SignedRelayedNodeInfo { &self.relay_info, self.timestamp, )?; - let validated_node_ids = + let opt_validated_node_ids = crypto.verify_signatures(node_ids, &node_info_bytes, &self.signatures)?; + let Some(validated_node_ids) = opt_validated_node_ids else { + apibail_generic!("verification error in relayed node info"); + }; if validated_node_ids.is_empty() { apibail_generic!("no valid node ids in relayed node info"); } diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_get_value.rs b/veilid-core/src/rpc_processor/coders/operations/operation_get_value.rs index f56a3a42..1652b6fb 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_get_value.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_get_value.rs @@ -142,13 +142,16 @@ impl RPCOperationGetValueA { }; // And the signed value data - value + if !value .validate( descriptor.owner(), get_value_context.subkey, get_value_context.vcrypto.clone(), ) - .map_err(RPCError::protocol)?; + .map_err(RPCError::protocol)? + { + return Err(RPCError::protocol("signed value data did not validate")); + } } PeerInfo::validate_vec(&mut self.peers, validate_context.crypto.clone()); diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs b/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs index 45d00643..8cd58636 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs @@ -149,13 +149,16 @@ impl RPCOperationSetValueA { if let Some(value) = &self.value { // And the signed value data - value + if !value .validate( set_value_context.descriptor.owner(), set_value_context.subkey, set_value_context.vcrypto.clone(), ) - .map_err(RPCError::protocol)?; + .map_err(RPCError::protocol)? + { + return Err(RPCError::protocol("signed value data did not validate")); + } } PeerInfo::validate_vec(&mut self.peers, validate_context.crypto.clone()); diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs b/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs index 3eb12eed..261d8e36 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs @@ -88,9 +88,12 @@ impl RPCOperationWatchValueQ { self.count, self.watch_id, ); - vcrypto + if !vcrypto .verify(&self.watcher, &sig_data, &self.signature) - .map_err(RPCError::protocol)?; + .map_err(RPCError::protocol)? + { + return Err(RPCError::protocol("failed to validate watcher signature")); + } // Count is zero means cancelling, so there should always be a watch id in this case if self.count == 0 && self.watch_id.is_none() { diff --git a/veilid-core/src/storage_manager/mod.rs b/veilid-core/src/storage_manager/mod.rs index 3a325b4a..b705e262 100644 --- a/veilid-core/src/storage_manager/mod.rs +++ b/veilid-core/src/storage_manager/mod.rs @@ -332,7 +332,7 @@ impl StorageManager { if let Some(rpc_processor) = opt_rpc_processor { // Use the safety selection we opened the record with // Use the writer we opened with as the 'watcher' as well - let opt_owvresult = self + let opt_owvresult = match self .outbound_watch_value_cancel( rpc_processor, key, @@ -342,7 +342,16 @@ impl StorageManager { active_watch.id, active_watch.watch_node, ) - .await?; + .await + { + Ok(v) => v, + Err(e) => { + log_stor!(debug + "close record watch cancel failed: {}", e + ); + None + } + }; if let Some(owvresult) = opt_owvresult { if owvresult.expiration_ts.as_u64() != 0 { log_stor!(debug diff --git a/veilid-core/src/storage_manager/types/signed_value_data.rs b/veilid-core/src/storage_manager/types/signed_value_data.rs index ea1e8ec6..e89354f0 100644 --- a/veilid-core/src/storage_manager/types/signed_value_data.rs +++ b/veilid-core/src/storage_manager/types/signed_value_data.rs @@ -20,7 +20,7 @@ impl SignedValueData { owner: &PublicKey, subkey: ValueSubkey, vcrypto: CryptoSystemVersion, - ) -> VeilidAPIResult<()> { + ) -> VeilidAPIResult { let node_info_bytes = Self::make_signature_bytes(&self.value_data, owner, subkey)?; // validate signature vcrypto.verify(self.value_data.writer(), &node_info_bytes, &self.signature) diff --git a/veilid-core/src/storage_manager/types/signed_value_descriptor.rs b/veilid-core/src/storage_manager/types/signed_value_descriptor.rs index 0a10a383..10832c38 100644 --- a/veilid-core/src/storage_manager/types/signed_value_descriptor.rs +++ b/veilid-core/src/storage_manager/types/signed_value_descriptor.rs @@ -19,7 +19,12 @@ impl SignedValueDescriptor { pub fn validate(&self, vcrypto: CryptoSystemVersion) -> VeilidAPIResult<()> { // validate signature - vcrypto.verify(&self.owner, &self.schema_data, &self.signature)?; + if !vcrypto.verify(&self.owner, &self.schema_data, &self.signature)? { + apibail_parse_error!( + "failed to validate signature of signed value descriptor", + self.signature + ); + } // validate schema DHTSchema::try_from(self.schema_data.as_slice())?; Ok(()) diff --git a/veilid-core/src/veilid_api/json_api/crypto_system.rs b/veilid-core/src/veilid_api/json_api/crypto_system.rs index 6bb960a9..a4261f4c 100644 --- a/veilid-core/src/veilid_api/json_api/crypto_system.rs +++ b/veilid-core/src/veilid_api/json_api/crypto_system.rs @@ -18,6 +18,7 @@ pub struct CryptoSystemResponse { #[serde(tag = "cs_op")] pub enum CryptoSystemRequestOp { Release, + Kind, CachedDh { #[schemars(with = "String")] key: PublicKey, @@ -108,7 +109,7 @@ pub enum CryptoSystemRequestOp { #[schemars(with = "String")] data: Vec, #[schemars(with = "String")] - secret: Signature, + signature: Signature, }, AeadOverhead, DecryptAead { @@ -150,6 +151,10 @@ pub enum CryptoSystemRequestOp { pub enum CryptoSystemResponseOp { InvalidId, Release, + Kind { + #[schemars(with = "String")] + value: CryptoKind, + }, CachedDh { #[serde(flatten)] #[schemars(with = "ApiResult")] @@ -219,7 +224,7 @@ pub enum CryptoSystemResponseOp { }, Verify { #[serde(flatten)] - result: ApiResult<()>, + result: ApiResult, }, AeadOverhead { value: u32, diff --git a/veilid-core/src/veilid_api/json_api/mod.rs b/veilid-core/src/veilid_api/json_api/mod.rs index 507fb218..7b7a2d38 100644 --- a/veilid-core/src/veilid_api/json_api/mod.rs +++ b/veilid-core/src/veilid_api/json_api/mod.rs @@ -201,8 +201,8 @@ pub enum ResponseOp { CryptoSystem(CryptoSystemResponse), VerifySignatures { #[serde(flatten)] - #[schemars(with = "ApiResult>")] - result: ApiResultWithVecString, + #[schemars(with = "ApiResult>>")] + result: ApiResultWithOptVecString>, }, GenerateSignatures { #[serde(flatten)] @@ -308,6 +308,21 @@ where }, } +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum ApiResultWithOptVecString +where + T: Clone + fmt::Debug, +{ + Ok { + #[schemars(with = "Option>")] + value: T, + }, + Err { + error: VeilidAPIError, + }, +} + pub fn emit_schemas(out: &mut HashMap) { let schema_request = schema_for!(Request); let schema_recv_message = schema_for!(RecvMessage); diff --git a/veilid-core/src/veilid_api/json_api/process.rs b/veilid-core/src/veilid_api/json_api/process.rs index d342f959..c4d6d338 100644 --- a/veilid-core/src/veilid_api/json_api/process.rs +++ b/veilid-core/src/veilid_api/json_api/process.rs @@ -28,6 +28,15 @@ pub fn to_json_api_result_with_vec_string( } } +pub fn to_json_api_result_with_opt_vec_string( + r: VeilidAPIResult, +) -> json_api::ApiResultWithOptVecString { + match r { + Err(e) => json_api::ApiResultWithOptVecString::Err { error: e }, + Ok(v) => json_api::ApiResultWithOptVecString::Ok { value: v }, + } +} + pub fn to_json_api_result_with_vec_u8(r: VeilidAPIResult>) -> json_api::ApiResultWithVecU8 { match r { Err(e) => json_api::ApiResultWithVecU8::Err { error: e }, @@ -462,6 +471,7 @@ impl JsonRequestProcessor { self.release_crypto_system(csr.cs_id); CryptoSystemResponseOp::Release {} } + CryptoSystemRequestOp::Kind => CryptoSystemResponseOp::Kind { value: csv.kind() }, CryptoSystemRequestOp::CachedDh { key, secret } => CryptoSystemResponseOp::CachedDh { result: to_json_api_result_with_string(csv.cached_dh(&key, &secret)), }, @@ -532,8 +542,12 @@ impl JsonRequestProcessor { CryptoSystemRequestOp::Sign { key, secret, data } => CryptoSystemResponseOp::Sign { result: to_json_api_result_with_string(csv.sign(&key, &secret, &data)), }, - CryptoSystemRequestOp::Verify { key, data, secret } => CryptoSystemResponseOp::Verify { - result: to_json_api_result(csv.verify(&key, &data, &secret)), + CryptoSystemRequestOp::Verify { + key, + data, + signature, + } => CryptoSystemResponseOp::Verify { + result: to_json_api_result(csv.verify(&key, &data, &signature)), }, CryptoSystemRequestOp::AeadOverhead => CryptoSystemResponseOp::AeadOverhead { value: csv.aead_overhead() as u32, @@ -766,7 +780,7 @@ impl JsonRequestProcessor { } }; ResponseOp::VerifySignatures { - result: to_json_api_result_with_vec_string(crypto.verify_signatures( + result: to_json_api_result_with_opt_vec_string(crypto.verify_signatures( &node_ids, &data, &signatures, diff --git a/veilid-flutter/example/integration_test/app_test.dart b/veilid-flutter/example/integration_test/app_test.dart index 22b23224..eb391933 100644 --- a/veilid-flutter/example/integration_test/app_test.dart +++ b/veilid-flutter/example/integration_test/app_test.dart @@ -30,6 +30,8 @@ void main() { test('get cryptosystem', testGetCryptoSystem); test('get cryptosystem invalid', testGetCryptoSystemInvalid); test('hash and verify password', testHashAndVerifyPassword); + test('sign and verify signature', testSignAndVerifySignature); + test('sign and verify signatures', testSignAndVerifySignatures); }); group('Table DB Tests', () { diff --git a/veilid-flutter/example/integration_test/test_crypto.dart b/veilid-flutter/example/integration_test/test_crypto.dart index fd159326..04bd1554 100644 --- a/veilid-flutter/example/integration_test/test_crypto.dart +++ b/veilid-flutter/example/integration_test/test_crypto.dart @@ -32,6 +32,41 @@ Future testHashAndVerifyPassword() async { expect(await cs.verifyPassword(utf8.encode('abc1235'), phash), isFalse); } +Future testSignAndVerifySignature() async { + final cs = await Veilid.instance.bestCryptoSystem(); + final kp1 = await cs.generateKeyPair(); + final kp2 = await cs.generateKeyPair(); + + // Signature match + final sig = await cs.sign(kp1.key, kp1.secret, utf8.encode('abc123')); + expect(await cs.verify(kp1.key, utf8.encode('abc123'), sig), isTrue); + + // Signature mismatch + final sig2 = await cs.sign(kp1.key, kp1.secret, utf8.encode('abc1234')); + expect(await cs.verify(kp1.key, utf8.encode('abc1234'), sig2), isTrue); + expect(await cs.verify(kp1.key, utf8.encode('abc12345'), sig2), isFalse); + expect(await cs.verify(kp2.key, utf8.encode('abc1234'), sig2), isFalse); +} + +Future testSignAndVerifySignatures() async { + final cs = await Veilid.instance.bestCryptoSystem(); + final kind = cs.kind(); + final kp1 = await cs.generateKeyPair(); + + // Signature match + final sigs = await Veilid.instance.generateSignatures( + utf8.encode('abc123'), [TypedKeyPair.fromKeyPair(kind, kp1)]); + expect( + await Veilid.instance.verifySignatures( + [TypedKey(kind: kind, value: kp1.key)], utf8.encode('abc123'), sigs), + equals([TypedKey(kind: kind, value: kp1.key)])); + // Signature mismatch + expect( + await Veilid.instance.verifySignatures( + [TypedKey(kind: kind, value: kp1.key)], utf8.encode('abc1234'), sigs), + isNull); +} + Future testGenerateSharedSecret() async { final cs = await Veilid.instance.bestCryptoSystem(); diff --git a/veilid-flutter/example/macos/Podfile.lock b/veilid-flutter/example/macos/Podfile.lock index 88db902f..cd7fcd0a 100644 --- a/veilid-flutter/example/macos/Podfile.lock +++ b/veilid-flutter/example/macos/Podfile.lock @@ -27,7 +27,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 - path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0 PODFILE CHECKSUM: 73d2f470b1d889e27fcfda1d6e6efec66f98af3f diff --git a/veilid-flutter/lib/routing_context.dart b/veilid-flutter/lib/routing_context.dart index c097dbbe..3c789f26 100644 --- a/veilid-flutter/lib/routing_context.dart +++ b/veilid-flutter/lib/routing_context.dart @@ -115,6 +115,13 @@ extension DHTRecordDescriptorExt on DHTRecordDescriptor { return KeyPair(key: owner, secret: ownerSecret!); } + TypedKey? ownerTypedSecret() { + if (ownerSecret == null) { + return null; + } + return TypedKey(kind: key.kind, value: ownerSecret!); + } + TypedKeyPair? ownerTypedKeyPair() { if (ownerSecret == null) { return null; diff --git a/veilid-flutter/lib/veilid.dart b/veilid-flutter/lib/veilid.dart index fbcd6f54..058899a9 100644 --- a/veilid-flutter/lib/veilid.dart +++ b/veilid-flutter/lib/veilid.dart @@ -44,6 +44,10 @@ Object? veilidApiToEncodable(Object? value) { List Function(dynamic) jsonListConstructor( T Function(dynamic) jsonConstructor) => (dynamic j) => (j as List).map(jsonConstructor).toList(); +List? Function(dynamic) optJsonListConstructor( + T Function(dynamic) jsonConstructor) => + (dynamic j) => + j == null ? null : (j as List).map(jsonConstructor).toList(); ////////////////////////////////////// /// VeilidVersion @@ -152,8 +156,8 @@ abstract class Veilid { List validCryptoKinds(); Future getCryptoSystem(CryptoKind kind); Future bestCryptoSystem(); - Future> verifySignatures( - List nodeIds, Uint8List data, List signatures); + Future?> verifySignatures(List publicKeys, + Uint8List data, List signatures); Future> generateSignatures( Uint8List data, List keyPairs); Future generateKeyPair(CryptoKind kind); diff --git a/veilid-flutter/lib/veilid_crypto.dart b/veilid-flutter/lib/veilid_crypto.dart index 186d8e01..4dbe4e87 100644 --- a/veilid-flutter/lib/veilid_crypto.dart +++ b/veilid-flutter/lib/veilid_crypto.dart @@ -214,7 +214,7 @@ abstract class VeilidCryptoSystem { Future signWithKeyPair(KeyPair keyPair, Uint8List data) => sign(keyPair.key, keyPair.secret, data); - Future verify(PublicKey key, Uint8List data, Signature signature); + Future verify(PublicKey key, Uint8List data, Signature signature); Future aeadOverhead(); Future decryptAead(Uint8List body, Nonce nonce, SharedSecret sharedSecret, Uint8List? associatedData); diff --git a/veilid-flutter/lib/veilid_ffi.dart b/veilid-flutter/lib/veilid_ffi.dart index aa289b9d..b6b1effd 100644 --- a/veilid-flutter/lib/veilid_ffi.dart +++ b/veilid-flutter/lib/veilid_ffi.dart @@ -1154,7 +1154,7 @@ class VeilidCryptoSystemFFI extends VeilidCryptoSystem { } @override - Future verify( + Future verify( PublicKey key, Uint8List data, Signature signature) async { final nativeKey = jsonEncode(key).toNativeUtf8(); final nativeEncodedData = base64UrlNoPadEncode(data).toNativeUtf8(); @@ -1164,7 +1164,7 @@ class VeilidCryptoSystemFFI extends VeilidCryptoSystem { final sendPort = recvPort.sendPort; _ffi._cryptoVerify(sendPort.nativePort, _kind, nativeKey, nativeEncodedData, nativeSignature); - return processFutureVoid(recvPort.first); + return processFuturePlain(recvPort.first); } @override @@ -1742,7 +1742,7 @@ class VeilidFFI extends Veilid { VeilidCryptoSystemFFI._(this, _bestCryptoKind()); @override - Future> verifySignatures(List nodeIds, + Future?> verifySignatures(List nodeIds, Uint8List data, List signatures) async { final nativeNodeIds = jsonEncode(nodeIds).toNativeUtf8(); final nativeData = base64UrlNoPadEncode(data).toNativeUtf8(); @@ -1752,7 +1752,7 @@ class VeilidFFI extends Veilid { final sendPort = recvPort.sendPort; _verifySignatures( sendPort.nativePort, nativeNodeIds, nativeData, nativeSignatures); - return processFutureJson( + return processFutureOptJson( jsonListConstructor(TypedKey.fromJson), recvPort.first); } diff --git a/veilid-flutter/lib/veilid_js.dart b/veilid-flutter/lib/veilid_js.dart index 12660ae7..6a6c9a6b 100644 --- a/veilid-flutter/lib/veilid_js.dart +++ b/veilid-flutter/lib/veilid_js.dart @@ -359,7 +359,7 @@ class VeilidCryptoSystemJS extends VeilidCryptoSystem { ])))); @override - Future verify(PublicKey key, Uint8List data, Signature signature) => + Future verify(PublicKey key, Uint8List data, Signature signature) => _wrapApiPromise(js_util.callMethod(wasm, 'crypto_verify', [ _kind, jsonEncode(key), @@ -655,10 +655,10 @@ class VeilidJS extends Veilid { this, js_util.callMethod(wasm, 'best_crypto_kind', [])); @override - Future> verifySignatures(List nodeIds, + Future?> verifySignatures(List nodeIds, Uint8List data, List signatures) async => - jsonListConstructor(TypedKey.fromJson)(jsonDecode(await _wrapApiPromise( - js_util.callMethod(wasm, 'verify_signatures', [ + optJsonListConstructor(TypedKey.fromJson)(jsonDecode( + await _wrapApiPromise(js_util.callMethod(wasm, 'verify_signatures', [ jsonEncode(nodeIds), base64UrlNoPadEncode(data), jsonEncode(signatures) diff --git a/veilid-flutter/rust/src/dart_ffi.rs b/veilid-flutter/rust/src/dart_ffi.rs index d1a00caf..262ae3be 100644 --- a/veilid-flutter/rust/src/dart_ffi.rs +++ b/veilid-flutter/rust/src/dart_ffi.rs @@ -1525,8 +1525,8 @@ pub extern "C" fn crypto_verify( let csv = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string()) })?; - csv.verify(&key, &data, &signature)?; - APIRESULT_VOID + let out = csv.verify(&key, &data, &signature)?; + APIResult::Ok(out) }); } diff --git a/veilid-python/tests/test_crypto.py b/veilid-python/tests/test_crypto.py index 94754328..606a9e2c 100644 --- a/veilid-python/tests/test_crypto.py +++ b/veilid-python/tests/test_crypto.py @@ -45,6 +45,39 @@ async def test_hash_and_verify_password(api_connection: veilid.VeilidAPI): phash2 = await cs.hash_password(b"abc1234", salt) assert not await cs.verify_password(b"abc12345", phash) +@pytest.mark.asyncio +async def test_sign_and_verify_signature(api_connection: veilid.VeilidAPI): + cs = await api_connection.best_crypto_system() + async with cs: + kp1 = await cs.generate_key_pair() + kp2 = await cs.generate_key_pair() + + # Signature match + sig = await cs.sign(kp1.key(), kp1.secret(), b"abc123") + assert await cs.verify(kp1.key(), b"abc123", sig) + + # Signature mismatch + sig2 = await cs.sign(kp1.key(), kp1.secret(), b"abc1234") + assert await cs.verify(kp1.key(), b"abc1234", sig2) + assert not await cs.verify(kp1.key(), b"abc12345", sig2) + assert not await cs.verify(kp2.key(), b"abc1234", sig2) + + +@pytest.mark.asyncio +async def test_sign_and_verify_signatures(api_connection: veilid.VeilidAPI): + cs = await api_connection.best_crypto_system() + async with cs: + kind = await cs.kind() + kp1 = await cs.generate_key_pair() + + # Signature match + sigs = await api_connection.generate_signatures(b"abc123", [veilid.TypedKeyPair.from_value(kind, kp1)]) + keys = [veilid.TypedKey.from_value(kind,kp1.key())] + assert (await api_connection.verify_signatures(keys, b"abc123", sigs)) == keys + + # Signature mismatch + assert (await api_connection.verify_signatures([veilid.TypedKey.from_value(kind,kp1.key())], b"abc1234", sigs)) is None + @pytest.mark.asyncio async def test_generate_shared_secret(api_connection: veilid.VeilidAPI): diff --git a/veilid-python/veilid/api.py b/veilid-python/veilid/api.py index 3d2e29c0..d7c25be9 100644 --- a/veilid-python/veilid/api.py +++ b/veilid-python/veilid/api.py @@ -115,7 +115,7 @@ class TableDbTransaction(ABC): async def __aexit__(self, *excinfo): if not self.is_done(): await self.rollback() - + @abstractmethod def is_done(self) -> bool: pass @@ -186,6 +186,10 @@ class CryptoSystem(ABC): if not self.is_done(): await self.release() + @abstractmethod + def kind(self) -> types.CryptoKind: + pass + @abstractmethod def is_done(self) -> bool: pass @@ -267,7 +271,7 @@ class CryptoSystem(ABC): pass @abstractmethod - async def verify(self, key: types.PublicKey, data: bytes, signature: types.Signature): + async def verify(self, key: types.PublicKey, data: bytes, signature: types.Signature) -> bool: pass @abstractmethod @@ -384,7 +388,7 @@ class VeilidAPI(ABC): node_ids: list[types.TypedKey], data: bytes, signatures: list[types.TypedSignature], - ) -> list[types.TypedKey]: + ) -> Optional[list[types.TypedKey]]: pass @abstractmethod diff --git a/veilid-python/veilid/json_api.py b/veilid-python/veilid/json_api.py index 5745327e..f79d18e7 100644 --- a/veilid-python/veilid/json_api.py +++ b/veilid-python/veilid/json_api.py @@ -386,18 +386,21 @@ class _JsonVeilidAPI(VeilidAPI): async def verify_signatures( self, node_ids: list[TypedKey], data: bytes, signatures: list[TypedSignature] - ) -> list[TypedKey]: + ) -> Optional[list[TypedKey]]: + out = raise_api_result( + await self.send_ndjson_request( + Operation.VERIFY_SIGNATURES, + node_ids=node_ids, + data=data, + signatures=signatures, + ) + ) + if out is None: + return out return list( map( lambda x: TypedKey(x), - raise_api_result( - await self.send_ndjson_request( - Operation.VERIFY_SIGNATURES, - node_ids=node_ids, - data=data, - signatures=signatures, - ) - ), + out ) ) @@ -938,6 +941,18 @@ class _JsonCryptoSystem(CryptoSystem): # complain raise AssertionError("Should have released crypto system before dropping object") + + async def kind(self) -> CryptoKind: + return CryptoKind( + raise_api_result( + await self.api.send_ndjson_request( + Operation.CRYPTO_SYSTEM, + validate=validate_cs_op, + cs_id=self.cs_id, + cs_op=CryptoSystemOperation.KIND, + ) + ) + ) def is_done(self) -> bool: return self.done @@ -1160,7 +1175,7 @@ class _JsonCryptoSystem(CryptoSystem): ) async def verify(self, key: PublicKey, data: bytes, signature: Signature): - raise_api_result( + return raise_api_result( await self.api.send_ndjson_request( Operation.CRYPTO_SYSTEM, validate=validate_cs_op, diff --git a/veilid-python/veilid/operations.py b/veilid-python/veilid/operations.py index a41fb96b..24d26121 100644 --- a/veilid-python/veilid/operations.py +++ b/veilid-python/veilid/operations.py @@ -73,6 +73,7 @@ class TableDbTransactionOperation(StrEnum): class CryptoSystemOperation(StrEnum): INVALID_ID = "InvalidId" RELEASE = "Release" + KIND = "Kind" CACHED_DH = "CachedDh" COMPUTE_DH = "ComputeDh" GENERATE_SHARED_SECRET = "GenerateSharedSecret" diff --git a/veilid-python/veilid/schema/RecvMessage.json b/veilid-python/veilid/schema/RecvMessage.json index 21192210..ba59e0e7 100644 --- a/veilid-python/veilid/schema/RecvMessage.json +++ b/veilid-python/veilid/schema/RecvMessage.json @@ -1599,6 +1599,24 @@ } } }, + { + "type": "object", + "required": [ + "cs_op", + "value" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "Kind" + ] + }, + "value": { + "type": "string" + } + } + }, { "type": "object", "anyOf": [ @@ -2039,7 +2057,7 @@ ], "properties": { "value": { - "type": "null" + "type": "boolean" } } }, @@ -2205,12 +2223,12 @@ "anyOf": [ { "type": "object", - "required": [ - "value" - ], "properties": { "value": { - "type": "array", + "type": [ + "array", + "null" + ], "items": { "type": "string" } @@ -2450,7 +2468,7 @@ ], "properties": { "id": { - "description": "Operation Id (pairs with Request, or empty if unidirectional)", + "description": "Operation Id (pairs with Request, or empty if unidirectional).", "default": 0, "type": "integer", "format": "uint32", @@ -2465,10 +2483,11 @@ } }, { + "description": "An update from the veilid-core to the host application describing a change to the internal state of the Veilid node.", "type": "object", "oneOf": [ { - "description": "A VeilidCore log message with optional backtrace", + "description": "A VeilidCore log message with optional backtrace.", "type": "object", "required": [ "kind", @@ -2497,7 +2516,7 @@ } }, { - "description": "Direct statement blob passed to hosting application for processing", + "description": "Direct statement blob passed to hosting application for processing.", "type": "object", "required": [ "kind", @@ -2528,7 +2547,7 @@ } }, { - "description": "Direct question blob passed to hosting application for processing to send an eventual AppReply", + "description": "Direct question blob passed to hosting application for processing to send an eventual AppReply.", "type": "object", "required": [ "call_id", @@ -2563,6 +2582,7 @@ } }, { + "description": "Describe the attachment state of the Veilid node", "type": "object", "required": [ "kind", @@ -2578,17 +2598,25 @@ ] }, "local_network_ready": { + "description": "If attached and there are enough eachable nodes in the routing table to perform all the actions of the LocalNetwork RoutingDomain.", "type": "boolean" }, "public_internet_ready": { + "description": "If attached and there are enough eachable nodes in the routing table to perform all the actions of the PublicInternet RoutingDomain, including things like private/safety route allocation and DHT operations.", "type": "boolean" }, "state": { - "$ref": "#/definitions/AttachmentState" + "description": "The overall quality of the routing table if attached, or the current state the attachment state machine.", + "allOf": [ + { + "$ref": "#/definitions/AttachmentState" + } + ] } } }, { + "description": "Describe the current network state of the Veilid node", "type": "object", "required": [ "bps_down", @@ -2599,9 +2627,11 @@ ], "properties": { "bps_down": { + "description": "The total number of bytes per second used by Veilid currently in the download direction.", "type": "string" }, "bps_up": { + "description": "The total number of bytes per second used by Veilid currently in the upload direction.", "type": "string" }, "kind": { @@ -2611,17 +2641,20 @@ ] }, "peers": { + "description": "The list of most recently accessed peers. This is not an active connection table, nor is representative of the entire routing table.", "type": "array", "items": { "$ref": "#/definitions/PeerTableData" } }, "started": { + "description": "If the network has been started or not.", "type": "boolean" } } }, { + "description": "Describe changes to the Veilid node configuration Currently this is only ever emitted once, however we reserve the right to add the ability to change the configuration or have it changed by the Veilid node itself during runtime.", "type": "object", "required": [ "config", @@ -2629,7 +2662,12 @@ ], "properties": { "config": { - "$ref": "#/definitions/VeilidConfigInner" + "description": "If the Veilid node configuration has changed the full new config will be here.", + "allOf": [ + { + "$ref": "#/definitions/VeilidConfigInner" + } + ] }, "kind": { "type": "string", @@ -2640,6 +2678,7 @@ } }, { + "description": "Describe a private route change that has happened", "type": "object", "required": [ "dead_remote_routes", @@ -2648,12 +2687,14 @@ ], "properties": { "dead_remote_routes": { + "description": "If a private route that was imported has died, it is listed here.", "type": "array", "items": { "type": "string" } }, "dead_routes": { + "description": "If a private route that was allocated has died, it is listed here.", "type": "array", "items": { "type": "string" @@ -2668,6 +2709,7 @@ } }, { + "description": "Describe when DHT records have subkey values changed", "type": "object", "required": [ "count", @@ -2677,11 +2719,13 @@ ], "properties": { "count": { + "description": "The count remaining on the watch that triggered this value change If there is no watch and this is received, it will be set to u32::MAX If this value is zero, any watch present on the value has died.", "type": "integer", "format": "uint32", "minimum": 0.0 }, "key": { + "description": "The DHT Record key that changed", "type": "string" }, "kind": { @@ -2691,6 +2735,7 @@ ] }, "subkeys": { + "description": "The portion of the DHT Record's subkeys that have changed If the subkey range is empty, any watch present on the value has died.", "type": "array", "items": { "type": "array", @@ -2711,6 +2756,7 @@ } }, "value": { + "description": "The (optional) value data for the first subkey in the subkeys range If 'subkeys' is not a single value, other values than the first value must be retrieved with RoutingContext::get_dht_value().", "anyOf": [ { "$ref": "#/definitions/ValueData" @@ -2752,7 +2798,7 @@ ], "definitions": { "AttachmentState": { - "description": "Attachment abstraction for network 'signal strength'", + "description": "Attachment abstraction for network 'signal strength'.", "type": "string", "enum": [ "Detached", @@ -2949,7 +2995,7 @@ } }, "FourCC": { - "description": "FOURCC code", + "description": "FOURCC code.", "type": "array", "items": { "type": "integer", @@ -3023,6 +3069,7 @@ } }, "PeerTableData": { + "description": "Describe a recently accessed peer", "type": "object", "required": [ "node_ids", @@ -3031,16 +3078,23 @@ ], "properties": { "node_ids": { + "description": "The node ids used by this peer", "type": "array", "items": { "type": "string" } }, "peer_address": { + "description": "The peer's human readable address.", "type": "string" }, "peer_stats": { - "$ref": "#/definitions/PeerStats" + "description": "Statistics we have collected on this peer.", + "allOf": [ + { + "$ref": "#/definitions/PeerStats" + } + ] } } }, @@ -3100,10 +3154,10 @@ } }, "SafetySelection": { - "description": "The choice of safety route to include in compiled routes", + "description": "The choice of safety route to include in compiled routes.", "oneOf": [ { - "description": "Don't use a safety route, only specify the sequencing preference", + "description": "Don't use a safety route, only specify the sequencing preference.", "type": "object", "required": [ "Unsafe" @@ -3116,7 +3170,7 @@ "additionalProperties": false }, { - "description": "Use a safety route and parameters specified by a SafetySpec", + "description": "Use a safety route and parameters specified by a SafetySpec.", "type": "object", "required": [ "Safe" @@ -3131,7 +3185,7 @@ ] }, "SafetySpec": { - "description": "Options for safety routes (sender privacy)", + "description": "Options for safety routes (sender privacy).", "type": "object", "required": [ "hop_count", @@ -3140,20 +3194,20 @@ ], "properties": { "hop_count": { - "description": "must be greater than 0", + "description": "Must be greater than 0.", "type": "integer", "format": "uint", "minimum": 0.0 }, "preferred_route": { - "description": "preferred safety route set id if it still exists", + "description": "Preferred safety route set id if it still exists.", "type": [ "string", "null" ] }, "sequencing": { - "description": "prefer connection-oriented sequenced protocols", + "description": "Prefer connection-oriented sequenced protocols.", "allOf": [ { "$ref": "#/definitions/Sequencing" @@ -3161,7 +3215,7 @@ ] }, "stability": { - "description": "prefer reliability over speed", + "description": "Prefer reliability over speed.", "allOf": [ { "$ref": "#/definitions/Stability" @@ -3504,7 +3558,7 @@ ] }, "VeilidConfigApplication": { - "description": "Application configuration\n\nConfigure web access to the Progressive Web App (PWA)\n\nTo be implemented...", + "description": "Application configuration.\n\nConfigure web access to the Progressive Web App (PWA).\n\nTo be implemented...", "type": "object", "required": [ "http", @@ -3549,7 +3603,7 @@ } }, "VeilidConfigDHT": { - "description": "Configure the Distributed Hash Table (DHT)", + "description": "Configure the Distributed Hash Table (DHT).", "type": "object", "required": [ "get_value_count", @@ -3689,7 +3743,7 @@ } }, "VeilidConfigHTTP": { - "description": "Enable and configure HTTP access to the Veilid node\n\n```yaml http: enabled: false listen_address: ':5150' path: 'app\" url: 'https://localhost:5150' ```", + "description": "Enable and configure HTTP access to the Veilid node.\n\n```yaml http: enabled: false listen_address: ':5150' path: 'app\" url: 'https://localhost:5150' ```", "type": "object", "required": [ "enabled", @@ -3715,7 +3769,7 @@ } }, "VeilidConfigHTTPS": { - "description": "Enable and configure HTTPS access to the Veilid node\n\n```yaml https: enabled: false listen_address: ':5150' path: 'app' url: 'https://localhost:5150' ```", + "description": "Enable and configure HTTPS access to the Veilid node.\n\n```yaml https: enabled: false listen_address: ':5150' path: 'app' url: 'https://localhost:5150' ```", "type": "object", "required": [ "enabled", @@ -3914,7 +3968,7 @@ } }, "VeilidConfigProtocol": { - "description": "Configure Network Protocols\n\nVeilid can communicate over UDP, TCP, and Web Sockets.\n\nAll protocols are available by default, and the Veilid node will sort out which protocol is used for each peer connection.", + "description": "Configure Network Protocols.\n\nVeilid can communicate over UDP, TCP, and Web Sockets.\n\nAll protocols are available by default, and the Veilid node will sort out which protocol is used for each peer connection.", "type": "object", "required": [ "tcp", @@ -3938,7 +3992,7 @@ } }, "VeilidConfigRPC": { - "description": "Configure RPC", + "description": "Configure RPC.", "type": "object", "required": [ "concurrency", @@ -3992,7 +4046,7 @@ } }, "VeilidConfigRoutingTable": { - "description": "Configure the network routing table", + "description": "Configure the network routing table.", "type": "object", "required": [ "bootstrap", @@ -4051,7 +4105,7 @@ } }, "VeilidConfigTCP": { - "description": "Enable and configure TCP\n\n```yaml tcp: connect: true listen: true max_connections: 32 listen_address: ':5150' public_address: ''", + "description": "Enable and configure TCP.\n\n```yaml tcp: connect: true listen: true max_connections: 32 listen_address: ':5150' public_address: ''", "type": "object", "required": [ "connect", @@ -4083,7 +4137,7 @@ } }, "VeilidConfigTLS": { - "description": "Configure TLS\n\n```yaml tls: certificate_path: /path/to/cert private_key_path: /path/to/private/key connection_initial_timeout_ms: 2000", + "description": "Configure TLS.\n\n```yaml tls: certificate_path: /path/to/cert private_key_path: /path/to/private/key connection_initial_timeout_ms: 2000", "type": "object", "required": [ "certificate_path", @@ -4120,7 +4174,7 @@ } }, "VeilidConfigUDP": { - "description": "Enable and configure UDP\n\n```yaml udp: enabled: true socket_pool_size: 0 listen_address: ':5150' public_address: '' ```", + "description": "Enable and configure UDP.\n\n```yaml udp: enabled: true socket_pool_size: 0 listen_address: ':5150' public_address: '' ```", "type": "object", "required": [ "enabled", @@ -4148,7 +4202,7 @@ } }, "VeilidConfigWS": { - "description": "Enable and configure Web Sockets\n\n```yaml ws: connect: true listen: true max_connections: 32 listen_address: ':5150' path: 'ws' url: 'ws://localhost:5150/ws'", + "description": "Enable and configure Web Sockets.\n\n```yaml ws: connect: true listen: true max_connections: 32 listen_address: ':5150' path: 'ws' url: 'ws://localhost:5150/ws'", "type": "object", "required": [ "connect", @@ -4184,7 +4238,7 @@ } }, "VeilidConfigWSS": { - "description": "Enable and configure Secure Web Sockets\n\n```yaml wss: connect: true listen: false max_connections: 32 listen_address: ':5150' path: 'ws' url: ''", + "description": "Enable and configure Secure Web Sockets.\n\n```yaml wss: connect: true listen: false max_connections: 32 listen_address: ':5150' path: 'ws' url: ''", "type": "object", "required": [ "connect", @@ -4220,7 +4274,7 @@ } }, "VeilidLogLevel": { - "description": "Log level for VeilidCore", + "description": "Log level for VeilidCore.", "type": "string", "enum": [ "Error", @@ -4231,6 +4285,7 @@ ] }, "VeilidState": { + "description": "A queriable state of the internals of veilid-core.", "type": "object", "required": [ "attachment", @@ -4250,6 +4305,7 @@ } }, "VeilidStateAttachment": { + "description": "Describe the attachment state of the Veilid node", "type": "object", "required": [ "local_network_ready", @@ -4258,28 +4314,42 @@ ], "properties": { "local_network_ready": { + "description": "If attached and there are enough eachable nodes in the routing table to perform all the actions of the LocalNetwork RoutingDomain.", "type": "boolean" }, "public_internet_ready": { + "description": "If attached and there are enough eachable nodes in the routing table to perform all the actions of the PublicInternet RoutingDomain, including things like private/safety route allocation and DHT operations.", "type": "boolean" }, "state": { - "$ref": "#/definitions/AttachmentState" + "description": "The overall quality of the routing table if attached, or the current state the attachment state machine.", + "allOf": [ + { + "$ref": "#/definitions/AttachmentState" + } + ] } } }, "VeilidStateConfig": { + "description": "Describe changes to the Veilid node configuration Currently this is only ever emitted once, however we reserve the right to add the ability to change the configuration or have it changed by the Veilid node itself during runtime.", "type": "object", "required": [ "config" ], "properties": { "config": { - "$ref": "#/definitions/VeilidConfigInner" + "description": "If the Veilid node configuration has changed the full new config will be here.", + "allOf": [ + { + "$ref": "#/definitions/VeilidConfigInner" + } + ] } } }, "VeilidStateNetwork": { + "description": "Describe the current network state of the Veilid node", "type": "object", "required": [ "bps_down", @@ -4289,18 +4359,22 @@ ], "properties": { "bps_down": { + "description": "The total number of bytes per second used by Veilid currently in the download direction.", "type": "string" }, "bps_up": { + "description": "The total number of bytes per second used by Veilid currently in the upload direction.", "type": "string" }, "peers": { + "description": "The list of most recently accessed peers. This is not an active connection table, nor is representative of the entire routing table.", "type": "array", "items": { "$ref": "#/definitions/PeerTableData" } }, "started": { + "description": "If the network has been started or not.", "type": "boolean" } } diff --git a/veilid-python/veilid/schema/Request.json b/veilid-python/veilid/schema/Request.json index 19a342cc..1e9a8441 100644 --- a/veilid-python/veilid/schema/Request.json +++ b/veilid-python/veilid/schema/Request.json @@ -963,6 +963,20 @@ } } }, + { + "type": "object", + "required": [ + "cs_op" + ], + "properties": { + "cs_op": { + "type": "string", + "enum": [ + "Kind" + ] + } + } + }, { "type": "object", "required": [ @@ -1291,7 +1305,7 @@ "cs_op", "data", "key", - "secret" + "signature" ], "properties": { "cs_op": { @@ -1306,7 +1320,7 @@ "key": { "type": "string" }, - "secret": { + "signature": { "type": "string" } } @@ -1586,7 +1600,7 @@ ], "properties": { "id": { - "description": "Operation Id (pairs with Response, or empty if unidirectional)", + "description": "Operation Id (pairs with Response, or empty if unidirectional).", "default": 0, "type": "integer", "format": "uint32", @@ -1712,10 +1726,10 @@ } }, "SafetySelection": { - "description": "The choice of safety route to include in compiled routes", + "description": "The choice of safety route to include in compiled routes.", "oneOf": [ { - "description": "Don't use a safety route, only specify the sequencing preference", + "description": "Don't use a safety route, only specify the sequencing preference.", "type": "object", "required": [ "Unsafe" @@ -1728,7 +1742,7 @@ "additionalProperties": false }, { - "description": "Use a safety route and parameters specified by a SafetySpec", + "description": "Use a safety route and parameters specified by a SafetySpec.", "type": "object", "required": [ "Safe" @@ -1743,7 +1757,7 @@ ] }, "SafetySpec": { - "description": "Options for safety routes (sender privacy)", + "description": "Options for safety routes (sender privacy).", "type": "object", "required": [ "hop_count", @@ -1752,20 +1766,20 @@ ], "properties": { "hop_count": { - "description": "must be greater than 0", + "description": "Must be greater than 0.", "type": "integer", "format": "uint", "minimum": 0.0 }, "preferred_route": { - "description": "preferred safety route set id if it still exists", + "description": "Preferred safety route set id if it still exists.", "type": [ "string", "null" ] }, "sequencing": { - "description": "prefer connection-oriented sequenced protocols", + "description": "Prefer connection-oriented sequenced protocols.", "allOf": [ { "$ref": "#/definitions/Sequencing" @@ -1773,7 +1787,7 @@ ] }, "stability": { - "description": "prefer reliability over speed", + "description": "Prefer reliability over speed.", "allOf": [ { "$ref": "#/definitions/Stability" diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 76d95ab9..46ab6452 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -1353,14 +1353,14 @@ pub fn crypto_verify(kind: u32, key: String, data: String, signature: String) -> .unwrap(); let signature: veilid_core::Signature = veilid_core::deserialize_json(&signature).unwrap(); - wrap_api_future_void(async move { + wrap_api_future_plain(async move { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; let csv = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string()) })?; - csv.verify(&key, &data, &signature)?; - APIRESULT_UNDEFINED + let out = csv.verify(&key, &data, &signature)?; + APIResult::Ok(out) }) } diff --git a/veilid-wasm/src/veilid_crypto_js.rs b/veilid-wasm/src/veilid_crypto_js.rs index 5933f39b..55c4d82d 100644 --- a/veilid-wasm/src/veilid_crypto_js.rs +++ b/veilid-wasm/src/veilid_crypto_js.rs @@ -207,7 +207,7 @@ impl VeilidCrypto { node_ids: StringArray, data: Box<[u8]>, signatures: StringArray, - ) -> VeilidAPIResult { + ) -> VeilidAPIResult> { let node_ids = into_unchecked_string_vec(node_ids); let node_ids: Vec = node_ids .iter() @@ -238,12 +238,15 @@ impl VeilidCrypto { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; - let out = crypto.verify_signatures(&node_ids, &data, &typed_signatures)?; - let out = out - .iter() - .map(|item| item.to_string()) - .collect::>(); - let out = into_unchecked_string_array(out); + let out = crypto + .verify_signatures(&node_ids, &data, &typed_signatures)? + .map(|sigs| { + let out = sigs + .iter() + .map(|item| item.to_string()) + .collect::>(); + into_unchecked_string_array(out) + }); APIResult::Ok(out) } @@ -375,7 +378,12 @@ impl VeilidCrypto { APIResult::Ok(out.to_string()) } - pub fn verify(kind: String, key: String, data: Box<[u8]>, signature: String) -> APIResult<()> { + pub fn verify( + kind: String, + key: String, + data: Box<[u8]>, + signature: String, + ) -> APIResult { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; @@ -386,8 +394,8 @@ impl VeilidCrypto { let crypto_system = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string()) })?; - crypto_system.verify(&key, &data, &signature)?; - APIRESULT_UNDEFINED + let out = crypto_system.verify(&key, &data, &signature)?; + APIResult::Ok(out) } pub fn aeadOverhead(kind: String) -> APIResult { diff --git a/veilid-wasm/tests/src/veilidCrypto.test.ts b/veilid-wasm/tests/src/veilidCrypto.test.ts index c325e052..00d1a49d 100644 --- a/veilid-wasm/tests/src/veilidCrypto.test.ts +++ b/veilid-wasm/tests/src/veilidCrypto.test.ts @@ -138,7 +138,7 @@ describe('veilidCrypto', () => { expect(() => { const res = veilidCrypto.verify(bestKind, publicKey, data, sig); - expect(res).toBeUndefined(); + expect(res).toBe(true); }).not.toThrow(); });