Merge branch veilid:main into dev-setup-macos/fix-java-version-detection

This commit is contained in:
Mike Phipps 2024-06-02 19:35:22 +00:00
commit 05d63f1eb6
37 changed files with 456 additions and 176 deletions

View File

@ -46,7 +46,7 @@ pub trait CryptoSystem {
// Authentication // Authentication
fn sign(&self, key: &PublicKey, secret: &SecretKey, data: &[u8]) -> VeilidAPIResult<Signature>; fn sign(&self, key: &PublicKey, secret: &SecretKey, data: &[u8]) -> VeilidAPIResult<Signature>;
fn verify(&self, key: &PublicKey, data: &[u8], signature: &Signature) -> VeilidAPIResult<()>; fn verify(&self, key: &PublicKey, data: &[u8], signature: &Signature) -> VeilidAPIResult<bool>;
// AEAD Encrypt/Decrypt // AEAD Encrypt/Decrypt
fn aead_overhead(&self) -> usize; fn aead_overhead(&self) -> usize;

View File

@ -172,9 +172,12 @@ impl Envelope {
); );
// Validate signature // Validate signature
vcrypto if !vcrypto
.verify(&sender_id, &data[0..(data.len() - 64)], &signature) .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 // Return envelope
Ok(Self { Ok(Self {

View File

@ -238,27 +238,28 @@ impl Crypto {
} }
/// Signature set verification /// Signature set verification
/// Returns the set of signature cryptokinds that validate and are supported /// Returns Some() the set of signature cryptokinds that validate and are supported
/// If any cryptokinds are supported and do not validate, the whole operation /// Returns None if any cryptokinds are supported and do not validate
/// returns an error
pub fn verify_signatures( pub fn verify_signatures(
&self, &self,
node_ids: &[TypedKey], public_keys: &[TypedKey],
data: &[u8], data: &[u8],
typed_signatures: &[TypedSignature], typed_signatures: &[TypedSignature],
) -> VeilidAPIResult<TypedKeyGroup> { ) -> VeilidAPIResult<Option<TypedKeyGroup>> {
let mut out = TypedKeyGroup::with_capacity(node_ids.len()); let mut out = TypedKeyGroup::with_capacity(public_keys.len());
for sig in typed_signatures { for sig in typed_signatures {
for nid in node_ids { for nid in public_keys {
if nid.kind == sig.kind { if nid.kind == sig.kind {
if let Some(vcrypto) = self.get(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); out.add(*nid);
} }
} }
} }
} }
Ok(out) Ok(Some(out))
} }
/// Signature set generation /// Signature set generation

View File

@ -143,13 +143,13 @@ impl CryptoSystem for CryptoSystemNONE {
// Validation // Validation
fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool { fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool {
let data = vec![0u8; 512]; let data = vec![0u8; 512];
let sig = match self.sign(dht_key, dht_key_secret, &data) { let Ok(sig) = self.sign(dht_key, dht_key_secret, &data) else {
Ok(s) => s, return false;
Err(_) => {
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 { fn validate_hash(&self, data: &[u8], dht_key: &PublicKey) -> bool {
let bytes = *blake3::hash(data).as_bytes(); let bytes = *blake3::hash(data).as_bytes();
@ -205,7 +205,7 @@ impl CryptoSystem for CryptoSystemNONE {
dht_key: &PublicKey, dht_key: &PublicKey,
data: &[u8], data: &[u8],
signature: &Signature, signature: &Signature,
) -> VeilidAPIResult<()> { ) -> VeilidAPIResult<bool> {
let mut dig = Blake3Digest512::new(); let mut dig = Blake3Digest512::new();
dig.update(data); dig.update(data);
let sig = dig.finalize(); 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])); .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) { if !is_bytes_eq_32(&verify_bytes[0..32], 0u8) {
return Err(VeilidAPIError::parse_error( return Ok(false);
"Verification failed",
"signature 0..32 is invalid",
));
} }
if !is_bytes_eq_32(&do_xor_32(&verify_bytes[32..64], &dht_key.bytes), 0xFFu8) { if !is_bytes_eq_32(&do_xor_32(&verify_bytes[32..64], &dht_key.bytes), 0xFFu8) {
return Err(VeilidAPIError::parse_error( return Ok(false);
"Verification failed",
"signature 32..64 is invalid",
));
} }
Ok(()) return Ok(true);
} }
// AEAD Encrypt/Decrypt // AEAD Encrypt/Decrypt

View File

@ -129,9 +129,12 @@ impl Receipt {
); );
// Validate signature // Validate signature
vcrypto if !vcrypto
.verify(&sender_id, &data[0..(data.len() - 64)], &signature) .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 // Get nonce
let nonce: Nonce = Nonce::new( let nonce: Nonce = Nonce::new(

View File

@ -64,49 +64,55 @@ pub async fn test_sign_and_verify(vcrypto: CryptoSystemVersion) {
assert_eq!( assert_eq!(
vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &a1), vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &a1),
Ok(()) Ok(true)
); );
assert_eq!( assert_eq!(
vcrypto.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &a2), 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 // Try verifications that should work
assert_eq!( assert_eq!(
vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig), vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig),
Ok(()) Ok(true)
); );
assert_eq!( assert_eq!(
vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig_b), vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig_b),
Ok(()) Ok(true)
); );
assert_eq!( assert_eq!(
vcrypto.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig2), vcrypto.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig2),
Ok(()) Ok(true)
); );
assert_eq!( assert_eq!(
vcrypto.verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig_c), vcrypto.verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig_c),
Ok(()) Ok(true)
); );
// Try verifications that shouldn't work // Try verifications that shouldn't work
assert!(vcrypto assert_eq!(
.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig) vcrypto.verify(&dht_key2, LOREM_IPSUM.as_bytes(), &dht_sig),
.is_err()); Ok(false)
assert!(vcrypto );
.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig2) assert_eq!(
.is_err()); vcrypto.verify(&dht_key, LOREM_IPSUM.as_bytes(), &dht_sig2),
assert!(vcrypto Ok(false)
.verify(&dht_key2, CHEEZBURGER.as_bytes(), &dht_sig_c) );
.is_err()); assert_eq!(
assert!(vcrypto vcrypto.verify(&dht_key2, CHEEZBURGER.as_bytes(), &dht_sig_c),
.verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig) Ok(false)
.is_err()); );
assert_eq!(
vcrypto.verify(&dht_key, CHEEZBURGER.as_bytes(), &dht_sig),
Ok(false)
);
} }
pub async fn test_key_conversions(vcrypto: CryptoSystemVersion) { pub async fn test_key_conversions(vcrypto: CryptoSystemVersion) {

View File

@ -161,13 +161,13 @@ impl CryptoSystem for CryptoSystemVLD0 {
// Validation // Validation
fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool { fn validate_keypair(&self, dht_key: &PublicKey, dht_key_secret: &SecretKey) -> bool {
let data = vec![0u8; 512]; let data = vec![0u8; 512];
let sig = match self.sign(dht_key, dht_key_secret, &data) { let Ok(sig) = self.sign(dht_key, dht_key_secret, &data) else {
Ok(s) => s, return false;
Err(_) => {
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 { fn validate_hash(&self, data: &[u8], dht_key: &PublicKey) -> bool {
let bytes = *blake3::hash(data).as_bytes(); let bytes = *blake3::hash(data).as_bytes();
@ -219,7 +219,9 @@ impl CryptoSystem for CryptoSystemVLD0 {
let sig = Signature::new(sig_bytes.to_bytes()); 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) Ok(sig)
} }
@ -228,7 +230,7 @@ impl CryptoSystem for CryptoSystemVLD0 {
dht_key: &PublicKey, dht_key: &PublicKey,
data: &[u8], data: &[u8],
signature: &Signature, signature: &Signature,
) -> VeilidAPIResult<()> { ) -> VeilidAPIResult<bool> {
let pk = ed::VerifyingKey::from_bytes(&dht_key.bytes) let pk = ed::VerifyingKey::from_bytes(&dht_key.bytes)
.map_err(|e| VeilidAPIError::parse_error("Public key is invalid", e))?; .map_err(|e| VeilidAPIError::parse_error("Public key is invalid", e))?;
let sig = ed::Signature::from_bytes(&signature.bytes); let sig = ed::Signature::from_bytes(&signature.bytes);
@ -236,9 +238,13 @@ impl CryptoSystem for CryptoSystemVLD0 {
let mut dig: ed::Sha512 = ed::Sha512::default(); let mut dig: ed::Sha512 = ed::Sha512::default();
dig.update(data); dig.update(data);
pk.verify_prehashed_strict(dig, Some(VEILID_DOMAIN_SIGN), &sig) if pk
.map_err(|e| VeilidAPIError::parse_error("Verification failed", e))?; .verify_prehashed_strict(dig, Some(VEILID_DOMAIN_SIGN), &sig)
Ok(()) .is_err()
{
return Ok(false);
}
Ok(true)
} }
// AEAD Encrypt/Decrypt // AEAD Encrypt/Decrypt

View File

@ -694,9 +694,16 @@ impl RouteSpecStore {
} }
} else { } else {
// Verify a signature for a hop node along the route // Verify a signature for a hop node along the route
if let Err(e) = vcrypto.verify(hop_public_key, data, &signatures[hop_n]) { match 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); Ok(true) => {}
return None; 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;
}
} }
} }
} }

View File

@ -27,8 +27,11 @@ impl SignedDirectNodeInfo {
let node_info_bytes = Self::make_signature_bytes(&self.node_info, self.timestamp)?; let node_info_bytes = Self::make_signature_bytes(&self.node_info, self.timestamp)?;
// Verify the signatures that we can // 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)?; 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() { if validated_node_ids.is_empty() {
apibail_generic!("no valid node ids in direct node info"); apibail_generic!("no valid node ids in direct node info");
} }

View File

@ -53,8 +53,11 @@ impl SignedRelayedNodeInfo {
&self.relay_info, &self.relay_info,
self.timestamp, self.timestamp,
)?; )?;
let validated_node_ids = let opt_validated_node_ids =
crypto.verify_signatures(node_ids, &node_info_bytes, &self.signatures)?; 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() { if validated_node_ids.is_empty() {
apibail_generic!("no valid node ids in relayed node info"); apibail_generic!("no valid node ids in relayed node info");
} }

View File

@ -142,13 +142,16 @@ impl RPCOperationGetValueA {
}; };
// And the signed value data // And the signed value data
value if !value
.validate( .validate(
descriptor.owner(), descriptor.owner(),
get_value_context.subkey, get_value_context.subkey,
get_value_context.vcrypto.clone(), 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()); PeerInfo::validate_vec(&mut self.peers, validate_context.crypto.clone());

View File

@ -149,13 +149,16 @@ impl RPCOperationSetValueA {
if let Some(value) = &self.value { if let Some(value) = &self.value {
// And the signed value data // And the signed value data
value if !value
.validate( .validate(
set_value_context.descriptor.owner(), set_value_context.descriptor.owner(),
set_value_context.subkey, set_value_context.subkey,
set_value_context.vcrypto.clone(), 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()); PeerInfo::validate_vec(&mut self.peers, validate_context.crypto.clone());

View File

@ -88,9 +88,12 @@ impl RPCOperationWatchValueQ {
self.count, self.count,
self.watch_id, self.watch_id,
); );
vcrypto if !vcrypto
.verify(&self.watcher, &sig_data, &self.signature) .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 // 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() { if self.count == 0 && self.watch_id.is_none() {

View File

@ -332,7 +332,7 @@ impl StorageManager {
if let Some(rpc_processor) = opt_rpc_processor { if let Some(rpc_processor) = opt_rpc_processor {
// Use the safety selection we opened the record with // Use the safety selection we opened the record with
// Use the writer we opened with as the 'watcher' as well // Use the writer we opened with as the 'watcher' as well
let opt_owvresult = self let opt_owvresult = match self
.outbound_watch_value_cancel( .outbound_watch_value_cancel(
rpc_processor, rpc_processor,
key, key,
@ -342,7 +342,16 @@ impl StorageManager {
active_watch.id, active_watch.id,
active_watch.watch_node, 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 let Some(owvresult) = opt_owvresult {
if owvresult.expiration_ts.as_u64() != 0 { if owvresult.expiration_ts.as_u64() != 0 {
log_stor!(debug log_stor!(debug

View File

@ -20,7 +20,7 @@ impl SignedValueData {
owner: &PublicKey, owner: &PublicKey,
subkey: ValueSubkey, subkey: ValueSubkey,
vcrypto: CryptoSystemVersion, vcrypto: CryptoSystemVersion,
) -> VeilidAPIResult<()> { ) -> VeilidAPIResult<bool> {
let node_info_bytes = Self::make_signature_bytes(&self.value_data, owner, subkey)?; let node_info_bytes = Self::make_signature_bytes(&self.value_data, owner, subkey)?;
// validate signature // validate signature
vcrypto.verify(self.value_data.writer(), &node_info_bytes, &self.signature) vcrypto.verify(self.value_data.writer(), &node_info_bytes, &self.signature)

View File

@ -19,7 +19,12 @@ impl SignedValueDescriptor {
pub fn validate(&self, vcrypto: CryptoSystemVersion) -> VeilidAPIResult<()> { pub fn validate(&self, vcrypto: CryptoSystemVersion) -> VeilidAPIResult<()> {
// validate signature // 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 // validate schema
DHTSchema::try_from(self.schema_data.as_slice())?; DHTSchema::try_from(self.schema_data.as_slice())?;
Ok(()) Ok(())

View File

@ -18,6 +18,7 @@ pub struct CryptoSystemResponse {
#[serde(tag = "cs_op")] #[serde(tag = "cs_op")]
pub enum CryptoSystemRequestOp { pub enum CryptoSystemRequestOp {
Release, Release,
Kind,
CachedDh { CachedDh {
#[schemars(with = "String")] #[schemars(with = "String")]
key: PublicKey, key: PublicKey,
@ -108,7 +109,7 @@ pub enum CryptoSystemRequestOp {
#[schemars(with = "String")] #[schemars(with = "String")]
data: Vec<u8>, data: Vec<u8>,
#[schemars(with = "String")] #[schemars(with = "String")]
secret: Signature, signature: Signature,
}, },
AeadOverhead, AeadOverhead,
DecryptAead { DecryptAead {
@ -150,6 +151,10 @@ pub enum CryptoSystemRequestOp {
pub enum CryptoSystemResponseOp { pub enum CryptoSystemResponseOp {
InvalidId, InvalidId,
Release, Release,
Kind {
#[schemars(with = "String")]
value: CryptoKind,
},
CachedDh { CachedDh {
#[serde(flatten)] #[serde(flatten)]
#[schemars(with = "ApiResult<String>")] #[schemars(with = "ApiResult<String>")]
@ -219,7 +224,7 @@ pub enum CryptoSystemResponseOp {
}, },
Verify { Verify {
#[serde(flatten)] #[serde(flatten)]
result: ApiResult<()>, result: ApiResult<bool>,
}, },
AeadOverhead { AeadOverhead {
value: u32, value: u32,

View File

@ -201,8 +201,8 @@ pub enum ResponseOp {
CryptoSystem(CryptoSystemResponse), CryptoSystem(CryptoSystemResponse),
VerifySignatures { VerifySignatures {
#[serde(flatten)] #[serde(flatten)]
#[schemars(with = "ApiResult<Vec<String>>")] #[schemars(with = "ApiResult<Option<Vec<String>>>")]
result: ApiResultWithVecString<TypedKeyGroup>, result: ApiResultWithOptVecString<Option<TypedKeyGroup>>,
}, },
GenerateSignatures { GenerateSignatures {
#[serde(flatten)] #[serde(flatten)]
@ -308,6 +308,21 @@ where
}, },
} }
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum ApiResultWithOptVecString<T>
where
T: Clone + fmt::Debug,
{
Ok {
#[schemars(with = "Option<Vec<String>>")]
value: T,
},
Err {
error: VeilidAPIError,
},
}
pub fn emit_schemas(out: &mut HashMap<String, String>) { pub fn emit_schemas(out: &mut HashMap<String, String>) {
let schema_request = schema_for!(Request); let schema_request = schema_for!(Request);
let schema_recv_message = schema_for!(RecvMessage); let schema_recv_message = schema_for!(RecvMessage);

View File

@ -28,6 +28,15 @@ pub fn to_json_api_result_with_vec_string<T: Clone + fmt::Debug>(
} }
} }
pub fn to_json_api_result_with_opt_vec_string<T: Clone + fmt::Debug>(
r: VeilidAPIResult<T>,
) -> json_api::ApiResultWithOptVecString<T> {
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<Vec<u8>>) -> json_api::ApiResultWithVecU8 { pub fn to_json_api_result_with_vec_u8(r: VeilidAPIResult<Vec<u8>>) -> json_api::ApiResultWithVecU8 {
match r { match r {
Err(e) => json_api::ApiResultWithVecU8::Err { error: e }, Err(e) => json_api::ApiResultWithVecU8::Err { error: e },
@ -462,6 +471,7 @@ impl JsonRequestProcessor {
self.release_crypto_system(csr.cs_id); self.release_crypto_system(csr.cs_id);
CryptoSystemResponseOp::Release {} CryptoSystemResponseOp::Release {}
} }
CryptoSystemRequestOp::Kind => CryptoSystemResponseOp::Kind { value: csv.kind() },
CryptoSystemRequestOp::CachedDh { key, secret } => CryptoSystemResponseOp::CachedDh { CryptoSystemRequestOp::CachedDh { key, secret } => CryptoSystemResponseOp::CachedDh {
result: to_json_api_result_with_string(csv.cached_dh(&key, &secret)), 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 { CryptoSystemRequestOp::Sign { key, secret, data } => CryptoSystemResponseOp::Sign {
result: to_json_api_result_with_string(csv.sign(&key, &secret, &data)), result: to_json_api_result_with_string(csv.sign(&key, &secret, &data)),
}, },
CryptoSystemRequestOp::Verify { key, data, secret } => CryptoSystemResponseOp::Verify { CryptoSystemRequestOp::Verify {
result: to_json_api_result(csv.verify(&key, &data, &secret)), key,
data,
signature,
} => CryptoSystemResponseOp::Verify {
result: to_json_api_result(csv.verify(&key, &data, &signature)),
}, },
CryptoSystemRequestOp::AeadOverhead => CryptoSystemResponseOp::AeadOverhead { CryptoSystemRequestOp::AeadOverhead => CryptoSystemResponseOp::AeadOverhead {
value: csv.aead_overhead() as u32, value: csv.aead_overhead() as u32,
@ -766,7 +780,7 @@ impl JsonRequestProcessor {
} }
}; };
ResponseOp::VerifySignatures { 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, &node_ids,
&data, &data,
&signatures, &signatures,

View File

@ -30,6 +30,8 @@ void main() {
test('get cryptosystem', testGetCryptoSystem); test('get cryptosystem', testGetCryptoSystem);
test('get cryptosystem invalid', testGetCryptoSystemInvalid); test('get cryptosystem invalid', testGetCryptoSystemInvalid);
test('hash and verify password', testHashAndVerifyPassword); test('hash and verify password', testHashAndVerifyPassword);
test('sign and verify signature', testSignAndVerifySignature);
test('sign and verify signatures', testSignAndVerifySignatures);
}); });
group('Table DB Tests', () { group('Table DB Tests', () {

View File

@ -32,6 +32,41 @@ Future<void> testHashAndVerifyPassword() async {
expect(await cs.verifyPassword(utf8.encode('abc1235'), phash), isFalse); expect(await cs.verifyPassword(utf8.encode('abc1235'), phash), isFalse);
} }
Future<void> 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<void> 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<void> testGenerateSharedSecret() async { Future<void> testGenerateSharedSecret() async {
final cs = await Veilid.instance.bestCryptoSystem(); final cs = await Veilid.instance.bestCryptoSystem();

View File

@ -27,7 +27,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0 veilid: a54f57b7bcf0e4e072fe99272d76ca126b2026d0
PODFILE CHECKSUM: 73d2f470b1d889e27fcfda1d6e6efec66f98af3f PODFILE CHECKSUM: 73d2f470b1d889e27fcfda1d6e6efec66f98af3f

View File

@ -115,6 +115,13 @@ extension DHTRecordDescriptorExt on DHTRecordDescriptor {
return KeyPair(key: owner, secret: ownerSecret!); return KeyPair(key: owner, secret: ownerSecret!);
} }
TypedKey? ownerTypedSecret() {
if (ownerSecret == null) {
return null;
}
return TypedKey(kind: key.kind, value: ownerSecret!);
}
TypedKeyPair? ownerTypedKeyPair() { TypedKeyPair? ownerTypedKeyPair() {
if (ownerSecret == null) { if (ownerSecret == null) {
return null; return null;

View File

@ -44,6 +44,10 @@ Object? veilidApiToEncodable(Object? value) {
List<T> Function(dynamic) jsonListConstructor<T>( List<T> Function(dynamic) jsonListConstructor<T>(
T Function(dynamic) jsonConstructor) => T Function(dynamic) jsonConstructor) =>
(dynamic j) => (j as List<dynamic>).map(jsonConstructor).toList(); (dynamic j) => (j as List<dynamic>).map(jsonConstructor).toList();
List<T>? Function(dynamic) optJsonListConstructor<T>(
T Function(dynamic) jsonConstructor) =>
(dynamic j) =>
j == null ? null : (j as List<dynamic>).map(jsonConstructor).toList();
////////////////////////////////////// //////////////////////////////////////
/// VeilidVersion /// VeilidVersion
@ -152,8 +156,8 @@ abstract class Veilid {
List<CryptoKind> validCryptoKinds(); List<CryptoKind> validCryptoKinds();
Future<VeilidCryptoSystem> getCryptoSystem(CryptoKind kind); Future<VeilidCryptoSystem> getCryptoSystem(CryptoKind kind);
Future<VeilidCryptoSystem> bestCryptoSystem(); Future<VeilidCryptoSystem> bestCryptoSystem();
Future<List<TypedKey>> verifySignatures( Future<List<TypedKey>?> verifySignatures(List<TypedKey> publicKeys,
List<TypedKey> nodeIds, Uint8List data, List<TypedSignature> signatures); Uint8List data, List<TypedSignature> signatures);
Future<List<TypedSignature>> generateSignatures( Future<List<TypedSignature>> generateSignatures(
Uint8List data, List<TypedKeyPair> keyPairs); Uint8List data, List<TypedKeyPair> keyPairs);
Future<TypedKeyPair> generateKeyPair(CryptoKind kind); Future<TypedKeyPair> generateKeyPair(CryptoKind kind);

View File

@ -214,7 +214,7 @@ abstract class VeilidCryptoSystem {
Future<Signature> signWithKeyPair(KeyPair keyPair, Uint8List data) => Future<Signature> signWithKeyPair(KeyPair keyPair, Uint8List data) =>
sign(keyPair.key, keyPair.secret, data); sign(keyPair.key, keyPair.secret, data);
Future<void> verify(PublicKey key, Uint8List data, Signature signature); Future<bool> verify(PublicKey key, Uint8List data, Signature signature);
Future<int> aeadOverhead(); Future<int> aeadOverhead();
Future<Uint8List> decryptAead(Uint8List body, Nonce nonce, Future<Uint8List> decryptAead(Uint8List body, Nonce nonce,
SharedSecret sharedSecret, Uint8List? associatedData); SharedSecret sharedSecret, Uint8List? associatedData);

View File

@ -1154,7 +1154,7 @@ class VeilidCryptoSystemFFI extends VeilidCryptoSystem {
} }
@override @override
Future<void> verify( Future<bool> verify(
PublicKey key, Uint8List data, Signature signature) async { PublicKey key, Uint8List data, Signature signature) async {
final nativeKey = jsonEncode(key).toNativeUtf8(); final nativeKey = jsonEncode(key).toNativeUtf8();
final nativeEncodedData = base64UrlNoPadEncode(data).toNativeUtf8(); final nativeEncodedData = base64UrlNoPadEncode(data).toNativeUtf8();
@ -1164,7 +1164,7 @@ class VeilidCryptoSystemFFI extends VeilidCryptoSystem {
final sendPort = recvPort.sendPort; final sendPort = recvPort.sendPort;
_ffi._cryptoVerify(sendPort.nativePort, _kind, nativeKey, nativeEncodedData, _ffi._cryptoVerify(sendPort.nativePort, _kind, nativeKey, nativeEncodedData,
nativeSignature); nativeSignature);
return processFutureVoid(recvPort.first); return processFuturePlain(recvPort.first);
} }
@override @override
@ -1742,7 +1742,7 @@ class VeilidFFI extends Veilid {
VeilidCryptoSystemFFI._(this, _bestCryptoKind()); VeilidCryptoSystemFFI._(this, _bestCryptoKind());
@override @override
Future<List<TypedKey>> verifySignatures(List<TypedKey> nodeIds, Future<List<TypedKey>?> verifySignatures(List<TypedKey> nodeIds,
Uint8List data, List<TypedSignature> signatures) async { Uint8List data, List<TypedSignature> signatures) async {
final nativeNodeIds = jsonEncode(nodeIds).toNativeUtf8(); final nativeNodeIds = jsonEncode(nodeIds).toNativeUtf8();
final nativeData = base64UrlNoPadEncode(data).toNativeUtf8(); final nativeData = base64UrlNoPadEncode(data).toNativeUtf8();
@ -1752,7 +1752,7 @@ class VeilidFFI extends Veilid {
final sendPort = recvPort.sendPort; final sendPort = recvPort.sendPort;
_verifySignatures( _verifySignatures(
sendPort.nativePort, nativeNodeIds, nativeData, nativeSignatures); sendPort.nativePort, nativeNodeIds, nativeData, nativeSignatures);
return processFutureJson( return processFutureOptJson(
jsonListConstructor<TypedKey>(TypedKey.fromJson), recvPort.first); jsonListConstructor<TypedKey>(TypedKey.fromJson), recvPort.first);
} }

View File

@ -359,7 +359,7 @@ class VeilidCryptoSystemJS extends VeilidCryptoSystem {
])))); ]))));
@override @override
Future<void> verify(PublicKey key, Uint8List data, Signature signature) => Future<bool> verify(PublicKey key, Uint8List data, Signature signature) =>
_wrapApiPromise(js_util.callMethod(wasm, 'crypto_verify', [ _wrapApiPromise(js_util.callMethod(wasm, 'crypto_verify', [
_kind, _kind,
jsonEncode(key), jsonEncode(key),
@ -655,10 +655,10 @@ class VeilidJS extends Veilid {
this, js_util.callMethod(wasm, 'best_crypto_kind', [])); this, js_util.callMethod(wasm, 'best_crypto_kind', []));
@override @override
Future<List<TypedKey>> verifySignatures(List<TypedKey> nodeIds, Future<List<TypedKey>?> verifySignatures(List<TypedKey> nodeIds,
Uint8List data, List<TypedSignature> signatures) async => Uint8List data, List<TypedSignature> signatures) async =>
jsonListConstructor(TypedKey.fromJson)(jsonDecode(await _wrapApiPromise( optJsonListConstructor(TypedKey.fromJson)(jsonDecode(
js_util.callMethod(wasm, 'verify_signatures', [ await _wrapApiPromise(js_util.callMethod(wasm, 'verify_signatures', [
jsonEncode(nodeIds), jsonEncode(nodeIds),
base64UrlNoPadEncode(data), base64UrlNoPadEncode(data),
jsonEncode(signatures) jsonEncode(signatures)

View File

@ -1525,8 +1525,8 @@ pub extern "C" fn crypto_verify(
let csv = crypto.get(kind).ok_or_else(|| { let csv = crypto.get(kind).ok_or_else(|| {
veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string()) veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string())
})?; })?;
csv.verify(&key, &data, &signature)?; let out = csv.verify(&key, &data, &signature)?;
APIRESULT_VOID APIResult::Ok(out)
}); });
} }

View File

@ -45,6 +45,39 @@ async def test_hash_and_verify_password(api_connection: veilid.VeilidAPI):
phash2 = await cs.hash_password(b"abc1234", salt) phash2 = await cs.hash_password(b"abc1234", salt)
assert not await cs.verify_password(b"abc12345", phash) 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 @pytest.mark.asyncio
async def test_generate_shared_secret(api_connection: veilid.VeilidAPI): async def test_generate_shared_secret(api_connection: veilid.VeilidAPI):

View File

@ -115,7 +115,7 @@ class TableDbTransaction(ABC):
async def __aexit__(self, *excinfo): async def __aexit__(self, *excinfo):
if not self.is_done(): if not self.is_done():
await self.rollback() await self.rollback()
@abstractmethod @abstractmethod
def is_done(self) -> bool: def is_done(self) -> bool:
pass pass
@ -186,6 +186,10 @@ class CryptoSystem(ABC):
if not self.is_done(): if not self.is_done():
await self.release() await self.release()
@abstractmethod
def kind(self) -> types.CryptoKind:
pass
@abstractmethod @abstractmethod
def is_done(self) -> bool: def is_done(self) -> bool:
pass pass
@ -267,7 +271,7 @@ class CryptoSystem(ABC):
pass pass
@abstractmethod @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 pass
@abstractmethod @abstractmethod
@ -384,7 +388,7 @@ class VeilidAPI(ABC):
node_ids: list[types.TypedKey], node_ids: list[types.TypedKey],
data: bytes, data: bytes,
signatures: list[types.TypedSignature], signatures: list[types.TypedSignature],
) -> list[types.TypedKey]: ) -> Optional[list[types.TypedKey]]:
pass pass
@abstractmethod @abstractmethod

View File

@ -386,18 +386,21 @@ class _JsonVeilidAPI(VeilidAPI):
async def verify_signatures( async def verify_signatures(
self, node_ids: list[TypedKey], data: bytes, signatures: list[TypedSignature] 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( return list(
map( map(
lambda x: TypedKey(x), lambda x: TypedKey(x),
raise_api_result( out
await self.send_ndjson_request(
Operation.VERIFY_SIGNATURES,
node_ids=node_ids,
data=data,
signatures=signatures,
)
),
) )
) )
@ -938,6 +941,18 @@ class _JsonCryptoSystem(CryptoSystem):
# complain # complain
raise AssertionError("Should have released crypto system before dropping object") 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: def is_done(self) -> bool:
return self.done return self.done
@ -1160,7 +1175,7 @@ class _JsonCryptoSystem(CryptoSystem):
) )
async def verify(self, key: PublicKey, data: bytes, signature: Signature): async def verify(self, key: PublicKey, data: bytes, signature: Signature):
raise_api_result( return raise_api_result(
await self.api.send_ndjson_request( await self.api.send_ndjson_request(
Operation.CRYPTO_SYSTEM, Operation.CRYPTO_SYSTEM,
validate=validate_cs_op, validate=validate_cs_op,

View File

@ -73,6 +73,7 @@ class TableDbTransactionOperation(StrEnum):
class CryptoSystemOperation(StrEnum): class CryptoSystemOperation(StrEnum):
INVALID_ID = "InvalidId" INVALID_ID = "InvalidId"
RELEASE = "Release" RELEASE = "Release"
KIND = "Kind"
CACHED_DH = "CachedDh" CACHED_DH = "CachedDh"
COMPUTE_DH = "ComputeDh" COMPUTE_DH = "ComputeDh"
GENERATE_SHARED_SECRET = "GenerateSharedSecret" GENERATE_SHARED_SECRET = "GenerateSharedSecret"

View File

@ -1599,6 +1599,24 @@
} }
} }
}, },
{
"type": "object",
"required": [
"cs_op",
"value"
],
"properties": {
"cs_op": {
"type": "string",
"enum": [
"Kind"
]
},
"value": {
"type": "string"
}
}
},
{ {
"type": "object", "type": "object",
"anyOf": [ "anyOf": [
@ -2039,7 +2057,7 @@
], ],
"properties": { "properties": {
"value": { "value": {
"type": "null" "type": "boolean"
} }
} }
}, },
@ -2205,12 +2223,12 @@
"anyOf": [ "anyOf": [
{ {
"type": "object", "type": "object",
"required": [
"value"
],
"properties": { "properties": {
"value": { "value": {
"type": "array", "type": [
"array",
"null"
],
"items": { "items": {
"type": "string" "type": "string"
} }
@ -2450,7 +2468,7 @@
], ],
"properties": { "properties": {
"id": { "id": {
"description": "Operation Id (pairs with Request, or empty if unidirectional)", "description": "Operation Id (pairs with Request, or empty if unidirectional).",
"default": 0, "default": 0,
"type": "integer", "type": "integer",
"format": "uint32", "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", "type": "object",
"oneOf": [ "oneOf": [
{ {
"description": "A VeilidCore log message with optional backtrace", "description": "A VeilidCore log message with optional backtrace.",
"type": "object", "type": "object",
"required": [ "required": [
"kind", "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", "type": "object",
"required": [ "required": [
"kind", "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", "type": "object",
"required": [ "required": [
"call_id", "call_id",
@ -2563,6 +2582,7 @@
} }
}, },
{ {
"description": "Describe the attachment state of the Veilid node",
"type": "object", "type": "object",
"required": [ "required": [
"kind", "kind",
@ -2578,17 +2598,25 @@
] ]
}, },
"local_network_ready": { "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" "type": "boolean"
}, },
"public_internet_ready": { "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" "type": "boolean"
}, },
"state": { "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", "type": "object",
"required": [ "required": [
"bps_down", "bps_down",
@ -2599,9 +2627,11 @@
], ],
"properties": { "properties": {
"bps_down": { "bps_down": {
"description": "The total number of bytes per second used by Veilid currently in the download direction.",
"type": "string" "type": "string"
}, },
"bps_up": { "bps_up": {
"description": "The total number of bytes per second used by Veilid currently in the upload direction.",
"type": "string" "type": "string"
}, },
"kind": { "kind": {
@ -2611,17 +2641,20 @@
] ]
}, },
"peers": { "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", "type": "array",
"items": { "items": {
"$ref": "#/definitions/PeerTableData" "$ref": "#/definitions/PeerTableData"
} }
}, },
"started": { "started": {
"description": "If the network has been started or not.",
"type": "boolean" "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", "type": "object",
"required": [ "required": [
"config", "config",
@ -2629,7 +2662,12 @@
], ],
"properties": { "properties": {
"config": { "config": {
"$ref": "#/definitions/VeilidConfigInner" "description": "If the Veilid node configuration has changed the full new config will be here.",
"allOf": [
{
"$ref": "#/definitions/VeilidConfigInner"
}
]
}, },
"kind": { "kind": {
"type": "string", "type": "string",
@ -2640,6 +2678,7 @@
} }
}, },
{ {
"description": "Describe a private route change that has happened",
"type": "object", "type": "object",
"required": [ "required": [
"dead_remote_routes", "dead_remote_routes",
@ -2648,12 +2687,14 @@
], ],
"properties": { "properties": {
"dead_remote_routes": { "dead_remote_routes": {
"description": "If a private route that was imported has died, it is listed here.",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"dead_routes": { "dead_routes": {
"description": "If a private route that was allocated has died, it is listed here.",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
@ -2668,6 +2709,7 @@
} }
}, },
{ {
"description": "Describe when DHT records have subkey values changed",
"type": "object", "type": "object",
"required": [ "required": [
"count", "count",
@ -2677,11 +2719,13 @@
], ],
"properties": { "properties": {
"count": { "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", "type": "integer",
"format": "uint32", "format": "uint32",
"minimum": 0.0 "minimum": 0.0
}, },
"key": { "key": {
"description": "The DHT Record key that changed",
"type": "string" "type": "string"
}, },
"kind": { "kind": {
@ -2691,6 +2735,7 @@
] ]
}, },
"subkeys": { "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", "type": "array",
"items": { "items": {
"type": "array", "type": "array",
@ -2711,6 +2756,7 @@
} }
}, },
"value": { "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": [ "anyOf": [
{ {
"$ref": "#/definitions/ValueData" "$ref": "#/definitions/ValueData"
@ -2752,7 +2798,7 @@
], ],
"definitions": { "definitions": {
"AttachmentState": { "AttachmentState": {
"description": "Attachment abstraction for network 'signal strength'", "description": "Attachment abstraction for network 'signal strength'.",
"type": "string", "type": "string",
"enum": [ "enum": [
"Detached", "Detached",
@ -2949,7 +2995,7 @@
} }
}, },
"FourCC": { "FourCC": {
"description": "FOURCC code", "description": "FOURCC code.",
"type": "array", "type": "array",
"items": { "items": {
"type": "integer", "type": "integer",
@ -3023,6 +3069,7 @@
} }
}, },
"PeerTableData": { "PeerTableData": {
"description": "Describe a recently accessed peer",
"type": "object", "type": "object",
"required": [ "required": [
"node_ids", "node_ids",
@ -3031,16 +3078,23 @@
], ],
"properties": { "properties": {
"node_ids": { "node_ids": {
"description": "The node ids used by this peer",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"peer_address": { "peer_address": {
"description": "The peer's human readable address.",
"type": "string" "type": "string"
}, },
"peer_stats": { "peer_stats": {
"$ref": "#/definitions/PeerStats" "description": "Statistics we have collected on this peer.",
"allOf": [
{
"$ref": "#/definitions/PeerStats"
}
]
} }
} }
}, },
@ -3100,10 +3154,10 @@
} }
}, },
"SafetySelection": { "SafetySelection": {
"description": "The choice of safety route to include in compiled routes", "description": "The choice of safety route to include in compiled routes.",
"oneOf": [ "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", "type": "object",
"required": [ "required": [
"Unsafe" "Unsafe"
@ -3116,7 +3170,7 @@
"additionalProperties": false "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", "type": "object",
"required": [ "required": [
"Safe" "Safe"
@ -3131,7 +3185,7 @@
] ]
}, },
"SafetySpec": { "SafetySpec": {
"description": "Options for safety routes (sender privacy)", "description": "Options for safety routes (sender privacy).",
"type": "object", "type": "object",
"required": [ "required": [
"hop_count", "hop_count",
@ -3140,20 +3194,20 @@
], ],
"properties": { "properties": {
"hop_count": { "hop_count": {
"description": "must be greater than 0", "description": "Must be greater than 0.",
"type": "integer", "type": "integer",
"format": "uint", "format": "uint",
"minimum": 0.0 "minimum": 0.0
}, },
"preferred_route": { "preferred_route": {
"description": "preferred safety route set id if it still exists", "description": "Preferred safety route set id if it still exists.",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"sequencing": { "sequencing": {
"description": "prefer connection-oriented sequenced protocols", "description": "Prefer connection-oriented sequenced protocols.",
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/Sequencing" "$ref": "#/definitions/Sequencing"
@ -3161,7 +3215,7 @@
] ]
}, },
"stability": { "stability": {
"description": "prefer reliability over speed", "description": "Prefer reliability over speed.",
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/Stability" "$ref": "#/definitions/Stability"
@ -3504,7 +3558,7 @@
] ]
}, },
"VeilidConfigApplication": { "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", "type": "object",
"required": [ "required": [
"http", "http",
@ -3549,7 +3603,7 @@
} }
}, },
"VeilidConfigDHT": { "VeilidConfigDHT": {
"description": "Configure the Distributed Hash Table (DHT)", "description": "Configure the Distributed Hash Table (DHT).",
"type": "object", "type": "object",
"required": [ "required": [
"get_value_count", "get_value_count",
@ -3689,7 +3743,7 @@
} }
}, },
"VeilidConfigHTTP": { "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", "type": "object",
"required": [ "required": [
"enabled", "enabled",
@ -3715,7 +3769,7 @@
} }
}, },
"VeilidConfigHTTPS": { "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", "type": "object",
"required": [ "required": [
"enabled", "enabled",
@ -3914,7 +3968,7 @@
} }
}, },
"VeilidConfigProtocol": { "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", "type": "object",
"required": [ "required": [
"tcp", "tcp",
@ -3938,7 +3992,7 @@
} }
}, },
"VeilidConfigRPC": { "VeilidConfigRPC": {
"description": "Configure RPC", "description": "Configure RPC.",
"type": "object", "type": "object",
"required": [ "required": [
"concurrency", "concurrency",
@ -3992,7 +4046,7 @@
} }
}, },
"VeilidConfigRoutingTable": { "VeilidConfigRoutingTable": {
"description": "Configure the network routing table", "description": "Configure the network routing table.",
"type": "object", "type": "object",
"required": [ "required": [
"bootstrap", "bootstrap",
@ -4051,7 +4105,7 @@
} }
}, },
"VeilidConfigTCP": { "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", "type": "object",
"required": [ "required": [
"connect", "connect",
@ -4083,7 +4137,7 @@
} }
}, },
"VeilidConfigTLS": { "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", "type": "object",
"required": [ "required": [
"certificate_path", "certificate_path",
@ -4120,7 +4174,7 @@
} }
}, },
"VeilidConfigUDP": { "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", "type": "object",
"required": [ "required": [
"enabled", "enabled",
@ -4148,7 +4202,7 @@
} }
}, },
"VeilidConfigWS": { "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", "type": "object",
"required": [ "required": [
"connect", "connect",
@ -4184,7 +4238,7 @@
} }
}, },
"VeilidConfigWSS": { "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", "type": "object",
"required": [ "required": [
"connect", "connect",
@ -4220,7 +4274,7 @@
} }
}, },
"VeilidLogLevel": { "VeilidLogLevel": {
"description": "Log level for VeilidCore", "description": "Log level for VeilidCore.",
"type": "string", "type": "string",
"enum": [ "enum": [
"Error", "Error",
@ -4231,6 +4285,7 @@
] ]
}, },
"VeilidState": { "VeilidState": {
"description": "A queriable state of the internals of veilid-core.",
"type": "object", "type": "object",
"required": [ "required": [
"attachment", "attachment",
@ -4250,6 +4305,7 @@
} }
}, },
"VeilidStateAttachment": { "VeilidStateAttachment": {
"description": "Describe the attachment state of the Veilid node",
"type": "object", "type": "object",
"required": [ "required": [
"local_network_ready", "local_network_ready",
@ -4258,28 +4314,42 @@
], ],
"properties": { "properties": {
"local_network_ready": { "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" "type": "boolean"
}, },
"public_internet_ready": { "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" "type": "boolean"
}, },
"state": { "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": { "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", "type": "object",
"required": [ "required": [
"config" "config"
], ],
"properties": { "properties": {
"config": { "config": {
"$ref": "#/definitions/VeilidConfigInner" "description": "If the Veilid node configuration has changed the full new config will be here.",
"allOf": [
{
"$ref": "#/definitions/VeilidConfigInner"
}
]
} }
} }
}, },
"VeilidStateNetwork": { "VeilidStateNetwork": {
"description": "Describe the current network state of the Veilid node",
"type": "object", "type": "object",
"required": [ "required": [
"bps_down", "bps_down",
@ -4289,18 +4359,22 @@
], ],
"properties": { "properties": {
"bps_down": { "bps_down": {
"description": "The total number of bytes per second used by Veilid currently in the download direction.",
"type": "string" "type": "string"
}, },
"bps_up": { "bps_up": {
"description": "The total number of bytes per second used by Veilid currently in the upload direction.",
"type": "string" "type": "string"
}, },
"peers": { "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", "type": "array",
"items": { "items": {
"$ref": "#/definitions/PeerTableData" "$ref": "#/definitions/PeerTableData"
} }
}, },
"started": { "started": {
"description": "If the network has been started or not.",
"type": "boolean" "type": "boolean"
} }
} }

View File

@ -963,6 +963,20 @@
} }
} }
}, },
{
"type": "object",
"required": [
"cs_op"
],
"properties": {
"cs_op": {
"type": "string",
"enum": [
"Kind"
]
}
}
},
{ {
"type": "object", "type": "object",
"required": [ "required": [
@ -1291,7 +1305,7 @@
"cs_op", "cs_op",
"data", "data",
"key", "key",
"secret" "signature"
], ],
"properties": { "properties": {
"cs_op": { "cs_op": {
@ -1306,7 +1320,7 @@
"key": { "key": {
"type": "string" "type": "string"
}, },
"secret": { "signature": {
"type": "string" "type": "string"
} }
} }
@ -1586,7 +1600,7 @@
], ],
"properties": { "properties": {
"id": { "id": {
"description": "Operation Id (pairs with Response, or empty if unidirectional)", "description": "Operation Id (pairs with Response, or empty if unidirectional).",
"default": 0, "default": 0,
"type": "integer", "type": "integer",
"format": "uint32", "format": "uint32",
@ -1712,10 +1726,10 @@
} }
}, },
"SafetySelection": { "SafetySelection": {
"description": "The choice of safety route to include in compiled routes", "description": "The choice of safety route to include in compiled routes.",
"oneOf": [ "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", "type": "object",
"required": [ "required": [
"Unsafe" "Unsafe"
@ -1728,7 +1742,7 @@
"additionalProperties": false "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", "type": "object",
"required": [ "required": [
"Safe" "Safe"
@ -1743,7 +1757,7 @@
] ]
}, },
"SafetySpec": { "SafetySpec": {
"description": "Options for safety routes (sender privacy)", "description": "Options for safety routes (sender privacy).",
"type": "object", "type": "object",
"required": [ "required": [
"hop_count", "hop_count",
@ -1752,20 +1766,20 @@
], ],
"properties": { "properties": {
"hop_count": { "hop_count": {
"description": "must be greater than 0", "description": "Must be greater than 0.",
"type": "integer", "type": "integer",
"format": "uint", "format": "uint",
"minimum": 0.0 "minimum": 0.0
}, },
"preferred_route": { "preferred_route": {
"description": "preferred safety route set id if it still exists", "description": "Preferred safety route set id if it still exists.",
"type": [ "type": [
"string", "string",
"null" "null"
] ]
}, },
"sequencing": { "sequencing": {
"description": "prefer connection-oriented sequenced protocols", "description": "Prefer connection-oriented sequenced protocols.",
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/Sequencing" "$ref": "#/definitions/Sequencing"
@ -1773,7 +1787,7 @@
] ]
}, },
"stability": { "stability": {
"description": "prefer reliability over speed", "description": "Prefer reliability over speed.",
"allOf": [ "allOf": [
{ {
"$ref": "#/definitions/Stability" "$ref": "#/definitions/Stability"

View File

@ -1353,14 +1353,14 @@ pub fn crypto_verify(kind: u32, key: String, data: String, signature: String) ->
.unwrap(); .unwrap();
let signature: veilid_core::Signature = veilid_core::deserialize_json(&signature).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 veilid_api = get_veilid_api()?;
let crypto = veilid_api.crypto()?; let crypto = veilid_api.crypto()?;
let csv = crypto.get(kind).ok_or_else(|| { let csv = crypto.get(kind).ok_or_else(|| {
veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string()) veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string())
})?; })?;
csv.verify(&key, &data, &signature)?; let out = csv.verify(&key, &data, &signature)?;
APIRESULT_UNDEFINED APIResult::Ok(out)
}) })
} }

View File

@ -207,7 +207,7 @@ impl VeilidCrypto {
node_ids: StringArray, node_ids: StringArray,
data: Box<[u8]>, data: Box<[u8]>,
signatures: StringArray, signatures: StringArray,
) -> VeilidAPIResult<StringArray> { ) -> VeilidAPIResult<Option<StringArray>> {
let node_ids = into_unchecked_string_vec(node_ids); let node_ids = into_unchecked_string_vec(node_ids);
let node_ids: Vec<TypedKey> = node_ids let node_ids: Vec<TypedKey> = node_ids
.iter() .iter()
@ -238,12 +238,15 @@ impl VeilidCrypto {
let veilid_api = get_veilid_api()?; let veilid_api = get_veilid_api()?;
let crypto = veilid_api.crypto()?; let crypto = veilid_api.crypto()?;
let out = crypto.verify_signatures(&node_ids, &data, &typed_signatures)?; let out = crypto
let out = out .verify_signatures(&node_ids, &data, &typed_signatures)?
.iter() .map(|sigs| {
.map(|item| item.to_string()) let out = sigs
.collect::<Vec<String>>(); .iter()
let out = into_unchecked_string_array(out); .map(|item| item.to_string())
.collect::<Vec<String>>();
into_unchecked_string_array(out)
});
APIResult::Ok(out) APIResult::Ok(out)
} }
@ -375,7 +378,12 @@ impl VeilidCrypto {
APIResult::Ok(out.to_string()) 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<bool> {
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?; let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?; 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(|| { let crypto_system = crypto.get(kind).ok_or_else(|| {
veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string()) veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string())
})?; })?;
crypto_system.verify(&key, &data, &signature)?; let out = crypto_system.verify(&key, &data, &signature)?;
APIRESULT_UNDEFINED APIResult::Ok(out)
} }
pub fn aeadOverhead(kind: String) -> APIResult<usize> { pub fn aeadOverhead(kind: String) -> APIResult<usize> {

View File

@ -138,7 +138,7 @@ describe('veilidCrypto', () => {
expect(() => { expect(() => {
const res = veilidCrypto.verify(bestKind, publicKey, data, sig); const res = veilidCrypto.verify(bestKind, publicKey, data, sig);
expect(res).toBeUndefined(); expect(res).toBe(true);
}).not.toThrow(); }).not.toThrow();
}); });