mirror of
https://gitlab.com/veilid/veilid.git
synced 2024-10-01 01:26:08 -04:00
improve route selection and add generate_shared_secret
This commit is contained in:
parent
d49e78d931
commit
800348451e
@ -1,5 +1,7 @@
|
||||
use super::*;
|
||||
|
||||
const VEILID_DOMAIN_API: &[u8] = b"VEILID_API";
|
||||
|
||||
pub trait CryptoSystem {
|
||||
// Accessors
|
||||
fn kind(&self) -> CryptoKind;
|
||||
@ -17,6 +19,15 @@ pub trait CryptoSystem {
|
||||
fn random_nonce(&self) -> Nonce;
|
||||
fn random_shared_secret(&self) -> SharedSecret;
|
||||
fn compute_dh(&self, key: &PublicKey, secret: &SecretKey) -> VeilidAPIResult<SharedSecret>;
|
||||
fn generate_shared_secret(
|
||||
&self,
|
||||
key: &PublicKey,
|
||||
secret: &SecretKey,
|
||||
domain: &[u8],
|
||||
) -> VeilidAPIResult<SharedSecret> {
|
||||
let dh = self.compute_dh(key, secret)?;
|
||||
Ok(self.generate_hash(&[&dh.bytes, domain, VEILID_DOMAIN_API].concat()))
|
||||
}
|
||||
fn generate_keypair(&self) -> KeyPair;
|
||||
fn generate_hash(&self, data: &[u8]) -> HashDigest;
|
||||
fn generate_hash_reader(&self, reader: &mut dyn std::io::Read) -> VeilidAPIResult<HashDigest>;
|
||||
|
@ -849,8 +849,7 @@ impl RouteSpecStore {
|
||||
// Get all valid routes, allow routes that need testing
|
||||
// but definitely prefer routes that have been recently tested
|
||||
for (id, rssd) in inner.content.iter_details() {
|
||||
if rssd.get_stability() >= stability
|
||||
&& rssd.is_sequencing_match(sequencing)
|
||||
if rssd.is_sequencing_match(sequencing)
|
||||
&& rssd.hop_count() >= min_hop_count
|
||||
&& rssd.hop_count() <= max_hop_count
|
||||
&& rssd.get_directions().is_superset(directions)
|
||||
@ -864,6 +863,7 @@ impl RouteSpecStore {
|
||||
|
||||
// Sort the routes by preference
|
||||
routes.sort_by(|a, b| {
|
||||
// Prefer routes that don't need testing
|
||||
let a_needs_testing = a.1.get_stats().needs_testing(cur_ts);
|
||||
let b_needs_testing = b.1.get_stats().needs_testing(cur_ts);
|
||||
if !a_needs_testing && b_needs_testing {
|
||||
@ -872,6 +872,18 @@ impl RouteSpecStore {
|
||||
if !b_needs_testing && a_needs_testing {
|
||||
return cmp::Ordering::Greater;
|
||||
}
|
||||
|
||||
// Prefer routes that meet the stability selection
|
||||
let a_meets_stability = a.1.get_stability() >= stability;
|
||||
let b_meets_stability = b.1.get_stability() >= stability;
|
||||
if a_meets_stability && !b_meets_stability {
|
||||
return cmp::Ordering::Less;
|
||||
}
|
||||
if b_meets_stability && !a_meets_stability {
|
||||
return cmp::Ordering::Greater;
|
||||
}
|
||||
|
||||
// Prefer faster routes
|
||||
let a_latency = a.1.get_stats().latency_stats().average;
|
||||
let b_latency = b.1.get_stats().latency_stats().average;
|
||||
|
||||
|
@ -30,6 +30,15 @@ pub enum CryptoSystemRequestOp {
|
||||
#[schemars(with = "String")]
|
||||
secret: SecretKey,
|
||||
},
|
||||
GenerateSharedSecret {
|
||||
#[schemars(with = "String")]
|
||||
key: PublicKey,
|
||||
#[schemars(with = "String")]
|
||||
secret: SecretKey,
|
||||
#[serde(with = "as_human_base64")]
|
||||
#[schemars(with = "String")]
|
||||
domain: Vec<u8>,
|
||||
},
|
||||
RandomBytes {
|
||||
len: u32,
|
||||
},
|
||||
@ -151,6 +160,11 @@ pub enum CryptoSystemResponseOp {
|
||||
#[schemars(with = "ApiResult<String>")]
|
||||
result: ApiResultWithString<SharedSecret>,
|
||||
},
|
||||
GenerateSharedSecret {
|
||||
#[serde(flatten)]
|
||||
#[schemars(with = "ApiResult<String>")]
|
||||
result: ApiResultWithString<SharedSecret>,
|
||||
},
|
||||
RandomBytes {
|
||||
#[serde(with = "as_human_base64")]
|
||||
#[schemars(with = "String")]
|
||||
|
@ -468,6 +468,16 @@ impl JsonRequestProcessor {
|
||||
CryptoSystemRequestOp::ComputeDh { key, secret } => CryptoSystemResponseOp::ComputeDh {
|
||||
result: to_json_api_result_with_string(csv.compute_dh(&key, &secret)),
|
||||
},
|
||||
CryptoSystemRequestOp::GenerateSharedSecret {
|
||||
key,
|
||||
secret,
|
||||
domain,
|
||||
} => CryptoSystemResponseOp::GenerateSharedSecret {
|
||||
result: to_json_api_result_with_string(
|
||||
csv.generate_shared_secret(&key, &secret, &domain),
|
||||
),
|
||||
},
|
||||
|
||||
CryptoSystemRequestOp::RandomBytes { len } => CryptoSystemResponseOp::RandomBytes {
|
||||
value: csv.random_bytes(len),
|
||||
},
|
||||
|
@ -32,3 +32,28 @@ Future<void> testHashAndVerifyPassword() async {
|
||||
await cs.hashPassword(utf8.encode("abc1234"), salt);
|
||||
expect(await cs.verifyPassword(utf8.encode("abc1235"), phash), isFalse);
|
||||
}
|
||||
|
||||
Future<void> testGenerateSharedSecret() async {
|
||||
final cs = await Veilid.instance.bestCryptoSystem();
|
||||
|
||||
final kp1 = await cs.generateKeyPair();
|
||||
final kp2 = await cs.generateKeyPair();
|
||||
final kp3 = await cs.generateKeyPair();
|
||||
|
||||
final ssA =
|
||||
await cs.generateSharedSecret(kp1.key, kp2.secret, utf8.encode("abc123"));
|
||||
final ssB =
|
||||
await cs.generateSharedSecret(kp2.key, kp1.secret, utf8.encode("abc123"));
|
||||
|
||||
expect(ssA, equals(ssB));
|
||||
|
||||
final ssC = await cs.generateSharedSecret(
|
||||
kp2.key, kp1.secret, utf8.encode("abc1234"));
|
||||
|
||||
expect(ssA, isNot(equals(ssC)));
|
||||
|
||||
final ssD =
|
||||
await cs.generateSharedSecret(kp3.key, kp1.secret, utf8.encode("abc123"));
|
||||
|
||||
expect(ssA, isNot(equals(ssD)));
|
||||
}
|
||||
|
@ -156,6 +156,8 @@ abstract class VeilidCryptoSystem {
|
||||
CryptoKind kind();
|
||||
Future<SharedSecret> cachedDH(PublicKey key, SecretKey secret);
|
||||
Future<SharedSecret> computeDH(PublicKey key, SecretKey secret);
|
||||
Future<SharedSecret> generateSharedSecret(
|
||||
PublicKey key, SecretKey secret, Uint8List domain);
|
||||
Future<Uint8List> randomBytes(int len);
|
||||
Future<int> defaultSaltLength();
|
||||
Future<String> hashPassword(Uint8List password, Uint8List salt);
|
||||
|
@ -163,6 +163,10 @@ typedef _CryptoCachedDHDart = void Function(
|
||||
// fn crypto_compute_dh(port: i64, kind: u32, key: FfiStr, secret: FfiStr)
|
||||
typedef _CryptoComputeDHDart = void Function(
|
||||
int, int, Pointer<Utf8>, Pointer<Utf8>);
|
||||
// fn crypto_generate_shared_secret(port: i64, kind: u32, key: FfiStr,
|
||||
// secret: FfiStr, domain: FfiStr)
|
||||
typedef _CryptoGenerateSharedSecretDart = void Function(
|
||||
int, int, Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>);
|
||||
// fn crypto_random_bytes(port: i64, kind: u32, len: u32)
|
||||
typedef _CryptoRandomBytesDart = void Function(int, int, int);
|
||||
// fn crypto_default_salt_length(port: i64, kind: u32)
|
||||
@ -998,6 +1002,20 @@ class VeilidCryptoSystemFFI extends VeilidCryptoSystem {
|
||||
return processFutureJson(SharedSecret.fromJson, recvPort.first);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SharedSecret> generateSharedSecret(
|
||||
PublicKey key, SecretKey secret, Uint8List domain) async {
|
||||
final nativeKey = jsonEncode(key).toNativeUtf8();
|
||||
final nativeSecret = jsonEncode(secret).toNativeUtf8();
|
||||
final nativeDomain = base64UrlNoPadEncode(domain).toNativeUtf8();
|
||||
|
||||
final recvPort = ReceivePort('crypto_generate_shared_secret');
|
||||
final sendPort = recvPort.sendPort;
|
||||
_ffi._cryptoGenerateSharedSecret(
|
||||
sendPort.nativePort, _kind, nativeKey, nativeSecret, nativeDomain);
|
||||
return processFutureJson(SharedSecret.fromJson, recvPort.first);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Uint8List> randomBytes(int len) async {
|
||||
final recvPort = ReceivePort('crypto_random_bytes');
|
||||
@ -1377,6 +1395,10 @@ class VeilidFFI extends Veilid {
|
||||
_cryptoComputeDH = dylib.lookupFunction<
|
||||
Void Function(Int64, Uint32, Pointer<Utf8>, Pointer<Utf8>),
|
||||
_CryptoComputeDHDart>('crypto_compute_dh'),
|
||||
_cryptoGenerateSharedSecret = dylib.lookupFunction<
|
||||
Void Function(
|
||||
Int64, Uint32, Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>),
|
||||
_CryptoGenerateSharedSecretDart>('crypto_generate_shared_secret'),
|
||||
_cryptoRandomBytes = dylib.lookupFunction<
|
||||
Void Function(Int64, Uint32, Uint32),
|
||||
_CryptoRandomBytesDart>('crypto_random_bytes'),
|
||||
@ -1516,6 +1538,7 @@ class VeilidFFI extends Veilid {
|
||||
|
||||
final _CryptoCachedDHDart _cryptoCachedDH;
|
||||
final _CryptoComputeDHDart _cryptoComputeDH;
|
||||
final _CryptoGenerateSharedSecretDart _cryptoGenerateSharedSecret;
|
||||
|
||||
final _CryptoRandomBytesDart _cryptoRandomBytes;
|
||||
final _CryptoDefaultSaltLengthDart _cryptoDefaultSaltLength;
|
||||
|
@ -271,6 +271,16 @@ class VeilidCryptoSystemJS extends VeilidCryptoSystem {
|
||||
wasm,
|
||||
'crypto_compute_dh',
|
||||
[_kind, jsonEncode(key), jsonEncode(secret)]))));
|
||||
@override
|
||||
Future<SharedSecret> generateSharedSecret(
|
||||
PublicKey key, SecretKey secret, Uint8List domain) async =>
|
||||
SharedSecret.fromJson(jsonDecode(await _wrapApiPromise(js_util.callMethod(
|
||||
wasm, 'crypto_generate_shared_secret', [
|
||||
_kind,
|
||||
jsonEncode(key),
|
||||
jsonEncode(secret),
|
||||
base64UrlNoPadEncode(domain)
|
||||
]))));
|
||||
|
||||
@override
|
||||
Future<Uint8List> randomBytes(int len) async =>
|
||||
|
@ -1186,6 +1186,39 @@ pub extern "C" fn crypto_compute_dh(port: i64, kind: u32, key: FfiStr, secret: F
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn crypto_generate_shared_secret(
|
||||
port: i64,
|
||||
kind: u32,
|
||||
key: FfiStr,
|
||||
secret: FfiStr,
|
||||
domain: FfiStr,
|
||||
) {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind);
|
||||
|
||||
let key: veilid_core::PublicKey =
|
||||
veilid_core::deserialize_opt_json(key.into_opt_string()).unwrap();
|
||||
let secret: veilid_core::SecretKey =
|
||||
veilid_core::deserialize_opt_json(secret.into_opt_string()).unwrap();
|
||||
let domain: Vec<u8> = data_encoding::BASE64URL_NOPAD
|
||||
.decode(domain.into_opt_string().unwrap().as_bytes())
|
||||
.unwrap();
|
||||
|
||||
DartIsolateWrapper::new(port).spawn_result_json(async move {
|
||||
let veilid_api = get_veilid_api().await?;
|
||||
let crypto = veilid_api.crypto()?;
|
||||
let csv = crypto.get(kind).ok_or_else(|| {
|
||||
veilid_core::VeilidAPIError::invalid_argument(
|
||||
"crypto_generate_shared_secret",
|
||||
"kind",
|
||||
kind.to_string(),
|
||||
)
|
||||
})?;
|
||||
let out = csv.generate_shared_secret(&key, &secret, &domain)?;
|
||||
APIResult::Ok(out)
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn crypto_random_bytes(port: i64, kind: u32, len: u32) {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind);
|
||||
|
@ -44,3 +44,26 @@ async def test_hash_and_verify_password(api_connection: veilid.VeilidAPI):
|
||||
# Password mismatch
|
||||
phash2 = await cs.hash_password(b"abc1234", salt)
|
||||
assert not await cs.verify_password(b"abc12345", phash)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_shared_secret(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()
|
||||
kp3 = await cs.generate_key_pair()
|
||||
|
||||
ssA = await cs.generate_shared_secret(kp1.key(), kp2.secret(), b"abc123")
|
||||
ssB = await cs.generate_shared_secret(kp2.key(), kp1.secret(), b"abc123")
|
||||
|
||||
assert ssA == ssB
|
||||
|
||||
ssC = await cs.generate_shared_secret(kp2.key(), kp1.secret(), b"abc1234")
|
||||
|
||||
assert ssA != ssC
|
||||
|
||||
ssD = await cs.generate_shared_secret(kp3.key(), kp1.secret(), b"abc123")
|
||||
|
||||
assert ssA != ssD
|
||||
|
||||
|
@ -65,7 +65,9 @@ async def test_routing_context_app_message_loopback():
|
||||
await api.debug("purge routes")
|
||||
|
||||
# make a routing context that uses a safety route
|
||||
rc = await api.new_routing_context()
|
||||
rc = await (await api.new_routing_context()).with_sequencing(
|
||||
veilid.Sequencing.ENSURE_ORDERED
|
||||
)
|
||||
async with rc:
|
||||
# make a new local private route
|
||||
prl, blob = await api.new_private_route()
|
||||
@ -113,7 +115,9 @@ async def test_routing_context_app_call_loopback():
|
||||
await api.debug("purge routes")
|
||||
|
||||
# make a routing context
|
||||
rc = await api.new_routing_context()
|
||||
rc = await (await api.new_routing_context()).with_sequencing(
|
||||
veilid.Sequencing.ENSURE_ORDERED
|
||||
)
|
||||
async with rc:
|
||||
# make a new local private route
|
||||
prl, blob = await api.new_private_route()
|
||||
@ -174,7 +178,9 @@ async def test_routing_context_app_message_loopback_big_packets():
|
||||
await api.debug("purge routes")
|
||||
|
||||
# make a routing context that uses a safety route
|
||||
rc = await api.new_routing_context()
|
||||
rc = await (await api.new_routing_context()).with_sequencing(
|
||||
veilid.Sequencing.ENSURE_ORDERED
|
||||
)
|
||||
async with rc:
|
||||
# make a new local private route
|
||||
prl, blob = await api.new_private_route()
|
||||
@ -291,7 +297,9 @@ async def test_routing_context_app_message_loopback_bandwidth():
|
||||
await api.debug("purge routes")
|
||||
|
||||
# make a routing context that uses a safety route
|
||||
rc = await api.new_routing_context()
|
||||
rc = await (await api.new_routing_context()).with_sequencing(
|
||||
veilid.Sequencing.ENSURE_ORDERED
|
||||
)
|
||||
async with rc:
|
||||
# make a new local private route
|
||||
prl, blob = await api.new_private_route()
|
||||
|
@ -204,6 +204,12 @@ class CryptoSystem(ABC):
|
||||
) -> types.SharedSecret:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def generate_shared_secret(
|
||||
self, key: types.PublicKey, secret: types.SecretKey, domain: bytes
|
||||
) -> types.SharedSecret:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def random_bytes(self, len: int) -> bytes:
|
||||
pass
|
||||
|
@ -977,6 +977,21 @@ class _JsonCryptoSystem(CryptoSystem):
|
||||
)
|
||||
)
|
||||
|
||||
async def generate_shared_secret(self, key: PublicKey, secret: SecretKey, domain: bytes) -> SharedSecret:
|
||||
return SharedSecret(
|
||||
raise_api_result(
|
||||
await self.api.send_ndjson_request(
|
||||
Operation.CRYPTO_SYSTEM,
|
||||
validate=validate_cs_op,
|
||||
cs_id=self.cs_id,
|
||||
cs_op=CryptoSystemOperation.GENERATE_SHARED_SECRET,
|
||||
key=key,
|
||||
secret=secret,
|
||||
domain=domain,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
async def random_bytes(self, len: int) -> bytes:
|
||||
return urlsafe_b64decode_no_pad(
|
||||
raise_api_result(
|
||||
|
@ -75,6 +75,7 @@ class CryptoSystemOperation(StrEnum):
|
||||
RELEASE = "Release"
|
||||
CACHED_DH = "CachedDh"
|
||||
COMPUTE_DH = "ComputeDh"
|
||||
GENERATE_SHARED_SECRET = "GenerateSharedSecret"
|
||||
RANDOM_BYTES = "RandomBytes"
|
||||
DEFAULT_SALT_LENGTH = "DefaultSaltLength"
|
||||
HASH_PASSWORD = "HashPassword"
|
||||
|
@ -1675,6 +1675,44 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"error"
|
||||
],
|
||||
"properties": {
|
||||
"error": {
|
||||
"$ref": "#/definitions/VeilidAPIError"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"required": [
|
||||
"cs_op"
|
||||
],
|
||||
"properties": {
|
||||
"cs_op": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"GenerateSharedSecret"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
@ -1007,6 +1007,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"cs_op",
|
||||
"domain",
|
||||
"key",
|
||||
"secret"
|
||||
],
|
||||
"properties": {
|
||||
"cs_op": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"GenerateSharedSecret"
|
||||
]
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"secret": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
@ -1048,6 +1048,36 @@ pub fn crypto_compute_dh(kind: u32, key: String, secret: String) -> Promise {
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen()]
|
||||
pub fn crypto_generate_shared_secret(
|
||||
kind: u32,
|
||||
key: String,
|
||||
secret: String,
|
||||
domain: String,
|
||||
) -> Promise {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind);
|
||||
|
||||
let key: veilid_core::PublicKey = veilid_core::deserialize_json(&key).unwrap();
|
||||
let secret: veilid_core::SecretKey = veilid_core::deserialize_json(&secret).unwrap();
|
||||
let domain: Vec<u8> = data_encoding::BASE64URL_NOPAD
|
||||
.decode(domain.as_bytes())
|
||||
.unwrap();
|
||||
|
||||
wrap_api_future_json(async move {
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let crypto = veilid_api.crypto()?;
|
||||
let csv = crypto.get(kind).ok_or_else(|| {
|
||||
veilid_core::VeilidAPIError::invalid_argument(
|
||||
"crypto_generate_shared_secret",
|
||||
"kind",
|
||||
kind.to_string(),
|
||||
)
|
||||
})?;
|
||||
let out = csv.generate_shared_secret(&key, &secret, &domain)?;
|
||||
APIResult::Ok(out)
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen()]
|
||||
pub fn crypto_random_bytes(kind: u32, len: u32) -> Promise {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind);
|
||||
|
@ -58,6 +58,30 @@ impl VeilidCrypto {
|
||||
APIResult::Ok(out.to_string())
|
||||
}
|
||||
|
||||
pub fn generateSharedSecret(
|
||||
kind: String,
|
||||
key: String,
|
||||
secret: String,
|
||||
domain: Box<[u8]>,
|
||||
) -> APIResult<String> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
|
||||
let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?;
|
||||
let secret: veilid_core::SecretKey = veilid_core::SecretKey::from_str(&secret)?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let crypto = veilid_api.crypto()?;
|
||||
let crypto_system = crypto.get(kind).ok_or_else(|| {
|
||||
veilid_core::VeilidAPIError::invalid_argument(
|
||||
"crypto_generate_shared_secret",
|
||||
"kind",
|
||||
kind.to_string(),
|
||||
)
|
||||
})?;
|
||||
let out = crypto_system.generate_shared_secret(&key, &secret, &domain)?;
|
||||
APIResult::Ok(out.to_string())
|
||||
}
|
||||
|
||||
pub fn randomBytes(kind: String, len: u32) -> APIResult<Box<[u8]>> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user