mirror of
https://gitlab.com/veilid/veilid.git
synced 2024-11-22 00:47:28 -06:00
Merge branch veilid:main into dev-setup-macos/fix-java-version-detection
This commit is contained in:
commit
05d63f1eb6
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
@ -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());
|
||||||
|
@ -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() {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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(())
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
@ -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', () {
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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> {
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user