diff --git a/veilid-core/src/crypto/crypto_system.rs b/veilid-core/src/crypto/crypto_system.rs index b8b30dc1..d97a87bc 100644 --- a/veilid-core/src/crypto/crypto_system.rs +++ b/veilid-core/src/crypto/crypto_system.rs @@ -16,11 +16,8 @@ pub trait CryptoSystem { fn random_bytes(&self, len: u32) -> Vec; fn default_salt_length(&self) -> u32; fn hash_password(&self, password: &[u8], salt: &[u8]) -> Result; - fn verify_password( - &self, - password: &[u8], - password_hash: String, - ) -> Result; + fn verify_password(&self, password: &[u8], password_hash: &str) + -> Result; fn derive_shared_secret( &self, password: &[u8], diff --git a/veilid-core/src/crypto/none/mod.rs b/veilid-core/src/crypto/none/mod.rs index 18af6b3a..7a2ac32f 100644 --- a/veilid-core/src/crypto/none/mod.rs +++ b/veilid-core/src/crypto/none/mod.rs @@ -103,7 +103,7 @@ impl CryptoSystem for CryptoSystemNONE { fn verify_password( &self, password: &[u8], - password_hash: String, + password_hash: &str, ) -> Result { let Some((salt, _)) = password_hash.split_once(":") else { apibail_generic!("invalid format"); @@ -111,7 +111,7 @@ impl CryptoSystem for CryptoSystemNONE { let Ok(salt) = BASE64URL_NOPAD.decode(salt.as_bytes()) else { apibail_generic!("invalid salt"); }; - return Ok(self.hash_password(password, &salt)? == password_hash); + return Ok(&self.hash_password(password, &salt)? == password_hash); } fn derive_shared_secret( diff --git a/veilid-core/src/crypto/tests/test_crypto.rs b/veilid-core/src/crypto/tests/test_crypto.rs index 469e3689..e7e0c73e 100644 --- a/veilid-core/src/crypto/tests/test_crypto.rs +++ b/veilid-core/src/crypto/tests/test_crypto.rs @@ -195,11 +195,11 @@ pub async fn test_generation(vcrypto: CryptoSystemVersion) { ) .expect_err("should reject long salt"); - assert!(vcrypto.verify_password(b"abc123", pstr1.clone()).unwrap()); - assert!(vcrypto.verify_password(b"abc123", pstr2.clone()).unwrap()); - assert!(vcrypto.verify_password(b"abc123", pstr3.clone()).unwrap()); - assert!(!vcrypto.verify_password(b"abc123", pstr4.clone()).unwrap()); - assert!(!vcrypto.verify_password(b"abc123", pstr5.clone()).unwrap()); + assert!(vcrypto.verify_password(b"abc123", &pstr1).unwrap()); + assert!(vcrypto.verify_password(b"abc123", &pstr2).unwrap()); + assert!(vcrypto.verify_password(b"abc123", &pstr3).unwrap()); + assert!(!vcrypto.verify_password(b"abc123", &pstr4).unwrap()); + assert!(!vcrypto.verify_password(b"abc123", &pstr5).unwrap()); let ss1 = vcrypto.derive_shared_secret(b"abc123", b"qwerasdf"); let ss2 = vcrypto.derive_shared_secret(b"abc123", b"qwerasdf"); diff --git a/veilid-core/src/crypto/vld0/mod.rs b/veilid-core/src/crypto/vld0/mod.rs index 04e4c5f6..1ead81c7 100644 --- a/veilid-core/src/crypto/vld0/mod.rs +++ b/veilid-core/src/crypto/vld0/mod.rs @@ -104,9 +104,9 @@ impl CryptoSystem for CryptoSystemVLD0 { fn verify_password( &self, password: &[u8], - password_hash: String, + password_hash: &str, ) -> Result { - let parsed_hash = PasswordHash::new(&password_hash).map_err(VeilidAPIError::generic)?; + let parsed_hash = PasswordHash::new(password_hash).map_err(VeilidAPIError::generic)?; // Argon2 with default params (Argon2id v19) let argon2 = Argon2::default(); diff --git a/veilid-flutter/example/.gitignore b/veilid-flutter/example/.gitignore index 0fa6b675..a1345d01 100644 --- a/veilid-flutter/example/.gitignore +++ b/veilid-flutter/example/.gitignore @@ -32,7 +32,6 @@ /build/ # Web related -lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols diff --git a/veilid-flutter/lib/veilid_crypto.dart b/veilid-flutter/lib/veilid_crypto.dart index f11f0ac9..881f98db 100644 --- a/veilid-flutter/lib/veilid_crypto.dart +++ b/veilid-flutter/lib/veilid_crypto.dart @@ -141,6 +141,11 @@ abstract class VeilidCryptoSystem { CryptoKind kind(); Future cachedDH(PublicKey key, SecretKey secret); Future computeDH(PublicKey key, SecretKey secret); + Future randomBytes(int len); + Future defaultSaltLength(); + Future hashPassword(Uint8List password, Uint8List salt); + Future verifyPassword(Uint8List password, String passwordHash); + Future deriveSharedSecret(Uint8List password, Uint8List salt); Future randomNonce(); Future randomSharedSecret(); Future generateKeyPair(); diff --git a/veilid-flutter/lib/veilid_ffi.dart b/veilid-flutter/lib/veilid_ffi.dart index 7597d7c0..004d6957 100644 --- a/veilid-flutter/lib/veilid_ffi.dart +++ b/veilid-flutter/lib/veilid_ffi.dart @@ -213,6 +213,28 @@ typedef _CryptoComputeDHC = Void Function( Int64, Uint32, Pointer, Pointer); typedef _CryptoComputeDHDart = void Function( int, int, Pointer, Pointer); +// fn crypto_random_bytes(port: i64, kind: u32, len: u32) +typedef _CryptoRandomBytesC = Void Function(Int64, Uint32, Uint32); +typedef _CryptoRandomBytesDart = void Function(int, int, int); +// fn crypto_default_salt_length(port: i64, kind: u32) +typedef _CryptoDefaultSaltLengthC = Void Function(Int64, Uint32); +typedef _CryptoDefaultSaltLengthDart = void Function(int, int); +// fn crypto_hash_password(port: i64, kind: u32, password: FfiStr, salt: FfiStr ) +typedef _CryptoHashPasswordC = Void Function( + Int64, Uint32, Pointer, Pointer); +typedef _CryptoHashPasswordDart = void Function( + int, int, Pointer, Pointer); +// fn crypto_verify_password(port: i64, kind: u32, password: FfiStr, password_hash: FfiStr ) +typedef _CryptoVerifyPasswordC = Void Function( + Int64, Uint32, Pointer, Pointer); +typedef _CryptoVerifyPasswordDart = void Function( + int, int, Pointer, Pointer); +// fn crypto_derive_shared_secret(port: i64, kind: u32, password: FfiStr, salt: FfiStr ) +typedef _CryptoDeriveSharedSecretC = Void Function( + Int64, Uint32, Pointer, Pointer); +typedef _CryptoDeriveSharedSecretDart = void Function( + int, int, Pointer, Pointer); + // fn crypto_random_nonce(port: i64, kind: u32) typedef _CryptoRandomNonceC = Void Function(Int64, Uint32); typedef _CryptoRandomNonceDart = void Function(int, int); @@ -884,7 +906,7 @@ class VeilidTableDBFFI extends VeilidTableDB { // FFI implementation of VeilidCryptoSystem class VeilidCryptoSystemFFI implements VeilidCryptoSystem { final CryptoKind _kind; - VeilidFFI _ffi; + final VeilidFFI _ffi; VeilidCryptoSystemFFI._(this._ffi, this._kind); @@ -915,6 +937,59 @@ class VeilidCryptoSystemFFI implements VeilidCryptoSystem { return processFutureJson(SharedSecret.fromJson, recvPort.first); } + @override + Future randomBytes(int len) async { + final recvPort = ReceivePort("crypto_random_bytes"); + final sendPort = recvPort.sendPort; + _ffi._cryptoRandomBytes(sendPort.nativePort, _kind, len); + final out = await processFuturePlain(recvPort.first); + return base64UrlNoPadDecode(out); + } + + @override + Future defaultSaltLength() { + final recvPort = ReceivePort("crypto_default_salt_length"); + final sendPort = recvPort.sendPort; + _ffi._cryptoDefaultSaltLength(sendPort.nativePort, _kind); + return processFuturePlain(recvPort.first); + } + + @override + Future hashPassword(Uint8List password, Uint8List salt) { + final nativeEncodedPassword = base64UrlNoPadEncode(password).toNativeUtf8(); + final nativeEncodedSalt = base64UrlNoPadEncode(salt).toNativeUtf8(); + + final recvPort = ReceivePort("crypto_hash_password"); + final sendPort = recvPort.sendPort; + _ffi._cryptoHashPassword( + sendPort.nativePort, _kind, nativeEncodedPassword, nativeEncodedSalt); + return processFuturePlain(recvPort.first); + } + + @override + Future verifyPassword(Uint8List password, String passwordHash) { + final nativeEncodedPassword = base64UrlNoPadEncode(password).toNativeUtf8(); + final nativeEncodedPasswordHash = passwordHash.toNativeUtf8(); + + final recvPort = ReceivePort("crypto_verify_password"); + final sendPort = recvPort.sendPort; + _ffi._cryptoVerifyPassword(sendPort.nativePort, _kind, + nativeEncodedPassword, nativeEncodedPasswordHash); + return processFuturePlain(recvPort.first); + } + + @override + Future deriveSharedSecret(Uint8List password, Uint8List salt) { + final nativeEncodedPassword = base64UrlNoPadEncode(password).toNativeUtf8(); + final nativeEncodedSalt = base64UrlNoPadEncode(salt).toNativeUtf8(); + + final recvPort = ReceivePort("crypto_derive_shared_secret"); + final sendPort = recvPort.sendPort; + _ffi._cryptoHashPassword( + sendPort.nativePort, _kind, nativeEncodedPassword, nativeEncodedSalt); + return processFutureJson(SharedSecret.fromJson, recvPort.first); + } + @override Future randomNonce() { final recvPort = ReceivePort("crypto_random_nonce"); @@ -1134,6 +1209,13 @@ class VeilidFFI implements Veilid { final _CryptoCachedDHDart _cryptoCachedDH; final _CryptoComputeDHDart _cryptoComputeDH; + + final _CryptoRandomBytesDart _cryptoRandomBytes; + final _CryptoDefaultSaltLengthDart _cryptoDefaultSaltLength; + final _CryptoHashPasswordDart _cryptoHashPassword; + final _CryptoVerifyPasswordDart _cryptoVerifyPassword; + final _CryptoDeriveSharedSecretDart _cryptoDeriveSharedSecret; + final _CryptoRandomNonceDart _cryptoRandomNonce; final _CryptoRandomSharedSecretDart _cryptoRandomSharedSecret; final _CryptoGenerateKeyPairDart _cryptoGenerateKeyPair; @@ -1295,6 +1377,20 @@ class VeilidFFI implements Veilid { _cryptoComputeDH = dylib.lookupFunction<_CryptoComputeDHC, _CryptoComputeDHDart>( 'crypto_compute_dh'), + _cryptoRandomBytes = + dylib.lookupFunction<_CryptoRandomBytesC, _CryptoRandomBytesDart>( + 'crypto_random_bytes'), + _cryptoDefaultSaltLength = dylib.lookupFunction< + _CryptoDefaultSaltLengthC, + _CryptoDefaultSaltLengthDart>('crypto_default_salt_length'), + _cryptoHashPassword = + dylib.lookupFunction<_CryptoHashPasswordC, _CryptoHashPasswordDart>( + 'crypto_hash_password'), + _cryptoVerifyPassword = dylib.lookupFunction<_CryptoVerifyPasswordC, + _CryptoVerifyPasswordDart>('crypto_verify_password'), + _cryptoDeriveSharedSecret = dylib.lookupFunction< + _CryptoDeriveSharedSecretC, + _CryptoVerifyPasswordDart>('crypto_derive_shared_secret'), _cryptoRandomNonce = dylib.lookupFunction<_CryptoRandomNonceC, _CryptoRandomNonceDart>( 'crypto_random_nonce'), diff --git a/veilid-flutter/lib/veilid_js.dart b/veilid-flutter/lib/veilid_js.dart index dad121af..8badebe6 100644 --- a/veilid-flutter/lib/veilid_js.dart +++ b/veilid-flutter/lib/veilid_js.dart @@ -157,7 +157,10 @@ class VeilidCryptoSystemJS implements VeilidCryptoSystem { final CryptoKind _kind; final VeilidJS _js; - VeilidCryptoSystemJS._(this._js, this._kind); + VeilidCryptoSystemJS._(this._js, this._kind) { + // Keep the reference + _js; + } @override CryptoKind kind() { @@ -178,6 +181,41 @@ class VeilidCryptoSystemJS implements VeilidCryptoSystem { [_kind, jsonEncode(key), jsonEncode(secret)])))); } + @override + Future randomBytes(int len) async { + return base64UrlNoPadDecode(await _wrapApiPromise( + js_util.callMethod(wasm, "crypto_random_bytes", [_kind, len]))); + } + + @override + Future defaultSaltLength() { + return _wrapApiPromise( + js_util.callMethod(wasm, "crypto_default_salt_length", [_kind])); + } + + @override + Future hashPassword(Uint8List password, Uint8List salt) { + return _wrapApiPromise(js_util.callMethod(wasm, "crypto_hash_password", + [_kind, base64UrlNoPadEncode(password), base64UrlNoPadEncode(salt)])); + } + + @override + Future verifyPassword(Uint8List password, String passwordHash) { + return _wrapApiPromise(js_util.callMethod(wasm, "crypto_verify_password", + [_kind, base64UrlNoPadEncode(password), passwordHash])); + } + + @override + Future deriveSharedSecret( + Uint8List password, Uint8List salt) async { + return SharedSecret.fromJson(jsonDecode(await _wrapApiPromise(js_util + .callMethod(wasm, "crypto_derive_shared_secret", [ + _kind, + base64UrlNoPadEncode(password), + base64UrlNoPadEncode(salt) + ])))); + } + @override Future randomNonce() async { return Nonce.fromJson(jsonDecode(await _wrapApiPromise( diff --git a/veilid-flutter/rust/src/dart_ffi.rs b/veilid-flutter/rust/src/dart_ffi.rs index 8e521ecf..d5ebd305 100644 --- a/veilid-flutter/rust/src/dart_ffi.rs +++ b/veilid-flutter/rust/src/dart_ffi.rs @@ -1091,6 +1091,109 @@ pub extern "C" fn crypto_compute_dh(port: i64, kind: u32, key: FfiStr, secret: F }); } + +#[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); + + DartIsolateWrapper::new(port).spawn_result(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_random_bytes", "kind", kind.to_string()))?; + let out = csv.random_bytes(len); + let out = data_encoding::BASE64URL_NOPAD.encode(&out); + APIResult::Ok(out) + }); +} + +#[no_mangle] +pub extern "C" fn crypto_default_salt_length(port: i64, kind: u32) { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind); + + DartIsolateWrapper::new(port).spawn_result(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_default_salt_length", "kind", kind.to_string()))?; + let out = csv.default_salt_length(); + APIResult::Ok(out) + }); +} + +#[no_mangle] +pub extern "C" fn crypto_hash_password(port: i64, kind: u32, password: FfiStr, salt: FfiStr ) { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind); + let password: Vec = data_encoding::BASE64URL_NOPAD + .decode( + password.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + let salt: Vec = data_encoding::BASE64URL_NOPAD + .decode( + salt.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + + DartIsolateWrapper::new(port).spawn_result(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_hash_password", "kind", kind.to_string()))?; + let out = csv.hash_password(&password, &salt)?; + APIResult::Ok(out) + }); +} + +#[no_mangle] +pub extern "C" fn crypto_verify_password(port: i64, kind: u32, password: FfiStr, password_hash: FfiStr ) { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind); + let password: Vec = data_encoding::BASE64URL_NOPAD + .decode( + password.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + let password_hash = password_hash.into_opt_string().unwrap(); + + DartIsolateWrapper::new(port).spawn_result(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_verify_password", "kind", kind.to_string()))?; + let out = csv.verify_password(&password, &password_hash)?; + APIResult::Ok(out) + }); +} + +#[no_mangle] +pub extern "C" fn crypto_derive_shared_secret(port: i64, kind: u32, password: FfiStr, salt: FfiStr ) { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind); + let password: Vec = data_encoding::BASE64URL_NOPAD + .decode( + password.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + let salt: Vec = data_encoding::BASE64URL_NOPAD + .decode( + salt.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_derive_shared_secret", "kind", kind.to_string()))?; + let out = csv.derive_shared_secret(&password, &salt)?; + APIResult::Ok(out) + }); +} + #[no_mangle] pub extern "C" fn crypto_random_nonce(port: i64, kind: u32) { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind); diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index 86a4126e..80703dd9 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -1014,6 +1014,117 @@ pub fn crypto_compute_dh(kind: u32, key: String, secret: String) -> Promise { }) } +#[wasm_bindgen()] +pub fn crypto_random_bytes(kind: u32, len: u32) -> Promise { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind); + + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_random_bytes", + "kind", + kind.to_string(), + ) + })?; + let out = csv.random_bytes(len); + let out = data_encoding::BASE64URL_NOPAD.encode(&out); + APIResult::Ok(out) + }) +} + +#[wasm_bindgen()] +pub fn crypto_default_salt_length(kind: u32) -> Promise { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind); + + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_default_salt_length", + "kind", + kind.to_string(), + ) + })?; + let out = csv.default_salt_length(); + APIResult::Ok(out) + }) +} + +#[wasm_bindgen()] +pub fn crypto_hash_password(kind: u32, password: String, salt: String) -> Promise { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind); + let password: Vec = data_encoding::BASE64URL_NOPAD + .decode(password.as_bytes()) + .unwrap(); + let salt: Vec = data_encoding::BASE64URL_NOPAD + .decode(salt.as_bytes()) + .unwrap(); + + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_hash_password", + "kind", + kind.to_string(), + ) + })?; + let out = csv.hash_password(&password, &salt)?; + APIResult::Ok(out) + }) +} + +#[wasm_bindgen()] +pub fn crypto_verify_password(kind: u32, password: String, password_hash: String) -> Promise { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind); + let password: Vec = data_encoding::BASE64URL_NOPAD + .decode(password.as_bytes()) + .unwrap(); + + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + let crypto = veilid_api.crypto()?; + let csv = crypto.get(kind).ok_or_else(|| { + veilid_core::VeilidAPIError::invalid_argument( + "crypto_verify_password", + "kind", + kind.to_string(), + ) + })?; + let out = csv.verify_password(&password, &password_hash)?; + APIResult::Ok(out) + }) +} + +#[wasm_bindgen()] +pub fn crypto_derive_shared_secret(kind: u32, password: String, salt: String) -> Promise { + let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind); + let password: Vec = data_encoding::BASE64URL_NOPAD + .decode(password.as_bytes()) + .unwrap(); + let salt: Vec = data_encoding::BASE64URL_NOPAD + .decode(salt.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_derive_shared_secret", + "kind", + kind.to_string(), + ) + })?; + let out = csv.derive_shared_secret(&password, &salt)?; + APIResult::Ok(out) + }) +} + #[wasm_bindgen()] pub fn crypto_random_nonce(kind: u32) -> Promise { let kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind);