diff --git a/melos.yaml b/melos.yaml index 6b868b96..977c09cb 100644 --- a/melos.yaml +++ b/melos.yaml @@ -3,9 +3,6 @@ repository: https://github.com/corbado/flutter-passkeys packages: - packages/** -command: - bootstrap: - usePubspecOverrides: true scripts: format: diff --git a/packages/corbado_auth/pubspec.yaml b/packages/corbado_auth/pubspec.yaml index 64bec2ab..a16f62f1 100644 --- a/packages/corbado_auth/pubspec.yaml +++ b/packages/corbado_auth/pubspec.yaml @@ -6,7 +6,7 @@ repository: https://github.com/corbado/flutter-passkeys/tree/main/packages/corba version: 3.7.1 environment: - sdk: ">=3.0.0 <4.0.0" + sdk: '>=3.8.0 <4.0.0' flutter: ">=3.0.0" dependencies: @@ -18,7 +18,7 @@ dependencies: sdk: flutter flutter_secure_storage: "<=9.0.0 >8.0.0" http: ^1.1.2 - json_annotation: ^4.8.1 + json_annotation: ^4.10.0 jwt_decoder: ^2.0.1 meta: ^1.15.0 passkeys: ^2.13.0 diff --git a/packages/passkeys/passkeys/example/ios/Flutter/AppFrameworkInfo.plist b/packages/passkeys/passkeys/example/ios/Flutter/AppFrameworkInfo.plist index 7c569640..1dc6cf76 100644 --- a/packages/passkeys/passkeys/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/passkeys/passkeys/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/packages/passkeys/passkeys/example/ios/Podfile b/packages/passkeys/passkeys/example/ios/Podfile index 2c068c40..10f3c9b4 100644 --- a/packages/passkeys/passkeys/example/ios/Podfile +++ b/packages/passkeys/passkeys/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/passkeys/passkeys/example/ios/Runner.xcodeproj/project.pbxproj b/packages/passkeys/passkeys/example/ios/Runner.xcodeproj/project.pbxproj index 4321e604..ae7f0f6d 100644 --- a/packages/passkeys/passkeys/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/passkeys/passkeys/example/ios/Runner.xcodeproj/project.pbxproj @@ -141,6 +141,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 1F5231B1C0B4B24BD6457DFB /* [CP] Embed Pods Frameworks */, + 635C969084CC2623F9C369FA /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -235,6 +236,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 635C969084CC2623F9C369FA /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -347,7 +365,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -430,7 +448,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -479,7 +497,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/packages/passkeys/passkeys/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/passkeys/passkeys/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c53e2b31..9c12df59 100644 --- a/packages/passkeys/passkeys/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/passkeys/passkeys/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> canAuthenticate() { return _platform.canAuthenticate(); } @@ -40,7 +40,7 @@ class PasskeyAuthenticator { /// Creates a new passkey and stores it on the device. /// Returns [RegisterResponseType] which must be sent to the relying party /// server. - Future register(RegisterRequestType request) async { + Future register(RegisterRequestType request, {String? salt}) async { if (debugMode) { await _doctor.check(request.relyingParty.id); } @@ -56,7 +56,7 @@ class PasskeyAuthenticator { _isValidCredentialID(credential.id); } - final r = await _platform.register(request); + final r = await _platform.register(request, salt); return r; } on PlatformException catch (e) { @@ -92,9 +92,7 @@ class PasskeyAuthenticator { /// Authenticates a user with a passkey. /// Returns [AuthenticateResponseType] which must be sent to the relying party /// server. - Future authenticate( - AuthenticateRequestType request, - ) async { + Future authenticate(AuthenticateRequestType request, {String? salt}) async { if (debugMode) { await _doctor.check(request.relyingPartyId); } @@ -110,7 +108,7 @@ class PasskeyAuthenticator { } } - final r = await _platform.authenticate(request); + final r = await _platform.authenticate(request, salt); return r; } on PlatformException catch (e) { @@ -174,9 +172,7 @@ class PasskeyAuthenticator { void _isValidChallenge(String challenge) { if (!_isValidBase64Url(input: challenge)) { if (debugMode) { - _doctor.recordException( - PlatformException(code: 'malformed-base64-url-challenge'), - ); + _doctor.recordException(PlatformException(code: 'malformed-base64-url-challenge')); } throw MalformedBase64UrlChallenge(); } @@ -186,9 +182,7 @@ class PasskeyAuthenticator { void _isValidCredentialID(String credentialID) { if (!_isValidBase64Url(input: credentialID)) { if (debugMode) { - _doctor.recordException( - PlatformException(code: 'malformed-base64-url-credential-id'), - ); + _doctor.recordException(PlatformException(code: 'malformed-base64-url-credential-id')); } throw MalformedBase64UrlCredentialID(); } @@ -198,9 +192,7 @@ class PasskeyAuthenticator { void _isValidUserID(String userID) { if (!_isValidBase64Url(input: userID, allowPadding: true)) { if (debugMode) { - _doctor.recordException( - PlatformException(code: 'malformed-base64-url-user-id'), - ); + _doctor.recordException(PlatformException(code: 'malformed-base64-url-user-id')); } throw MalformedBase64UrlUserID(); } @@ -227,8 +219,7 @@ class PasskeyAuthenticator { if (!base64UrlRegex.hasMatch(input)) return false; try { - String normalized = - input.padRight(input.length + (4 - input.length % 4) % 4, '='); + String normalized = input.padRight(input.length + (4 - input.length % 4) % 4, '='); base64Url.decode(normalized); return true; diff --git a/packages/passkeys/passkeys/pubspec.yaml b/packages/passkeys/passkeys/pubspec.yaml index 30490664..b5f9bafc 100644 --- a/packages/passkeys/passkeys/pubspec.yaml +++ b/packages/passkeys/passkeys/pubspec.yaml @@ -3,9 +3,11 @@ description: Flutter plugin enabling simple passkey authentication. Can be eithe homepage: https://docs.corbado.com/overview/welcome repository: https://github.com/corbado/flutter-passkeys/tree/main/packages/passkeys/passkeys version: 2.17.4 +publish_to: none + environment: - sdk: ">=3.0.0 <4.0.0" + sdk: '>=3.8.0 <4.0.0' flutter: ">=3.0.0" flutter: @@ -27,12 +29,18 @@ dependencies: flutter: sdk: flutter json_annotation: ^4.8.1 - passkeys_android: ^2.11.0 - passkeys_darwin: ^0.3.0 - passkeys_doctor: ^1.2.0 - passkeys_platform_interface: ^2.6.0 - passkeys_web: ^2.8.1 - passkeys_windows: ^0.1.1 + passkeys_android: + path: ../passkeys_android + passkeys_darwin: + path: ../passkeys_darwin + passkeys_doctor: + path: ../passkeys_doctor + passkeys_platform_interface: + path: ../passkeys_platform_interface + passkeys_web: + path: ../passkeys_web + passkeys_windows: + path: ../passkeys_windows ua_client_hints: ^1.1.3 dev_dependencies: @@ -43,3 +51,7 @@ dev_dependencies: mocktail: ^1.0.0 plugin_platform_interface: ^2.0.0 very_good_analysis: ^5.0.0 + +dependency_overrides: + passkeys_platform_interface: + path: ../passkeys_platform_interface \ No newline at end of file diff --git a/packages/passkeys/passkeys_android/android/src/main/java/com/corbado/passkeys_android/MessageHandler.java b/packages/passkeys/passkeys_android/android/src/main/java/com/corbado/passkeys_android/MessageHandler.java index 153b5339..458d301c 100644 --- a/packages/passkeys/passkeys_android/android/src/main/java/com/corbado/passkeys_android/MessageHandler.java +++ b/packages/passkeys/passkeys_android/android/src/main/java/com/corbado/passkeys_android/MessageHandler.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.os.CancellationSignal; +import android.util.Base64; import android.util.Log; import androidx.annotation.NonNull; @@ -41,7 +42,9 @@ import org.json.JSONObject; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -89,7 +92,9 @@ public void register( @Nullable Long timeout, @Nullable String attestation, @NonNull List excludeCredentials, - @NonNull Messages.Result result) { + @Nullable String salt, + @NonNull Messages.Result result + ) { if (android.os.Build.VERSION.SDK_INT < 28) { result.error(new Messages.FlutterError("android-passkey-unsupported", "Passkeys are only supported on Android API 28 and above.", null)); @@ -123,7 +128,24 @@ public void register( excludeCredentialsType); try { - String options = createCredentialOptions.toJSON().toString(); + JSONObject optionsJson = createCredentialOptions.toJSON(); + + // PRF extension if salt provided + if (salt != null && !salt.isEmpty()) { + String saltBase64Url = hexToBase64Url(salt); + JSONObject extensions = optionsJson.optJSONObject("extensions"); + if (extensions == null) extensions = new JSONObject(); + + JSONObject prf = new JSONObject(); + JSONObject eval = new JSONObject(); + eval.put("first", saltBase64Url); + prf.put("eval", eval); + + extensions.put("prf", prf); + optionsJson.put("extensions", extensions); + } + String options = optionsJson.toString(); + Log.i("Passkeys", "options = " + options); Activity activity = plugin.requireActivity(); CredentialManager credentialManager = CredentialManager.create(activity); @@ -158,12 +180,32 @@ public void onResult(CreateCredentialResponse res) { typedTransports.add(""); } + JSONObject ext = json.optJSONObject("clientExtensionResults"); + Map extMap = null; + + if (ext != null) { + extMap = new HashMap<>(); + + JSONObject prf = ext.optJSONObject("prf"); + if (prf != null) { + JSONObject results = prf.optJSONObject("results"); + if (results != null) { + String first = results.optString("first", ""); + Map resultsMap = new HashMap<>(); + resultsMap.put("first", first); + Map prfMap = new HashMap<>(); + prfMap.put("results", resultsMap); + extMap.put("prf", prfMap); + } + } + } result.success(new Messages.RegisterResponse.Builder() .setId(json.getString("id")) .setRawId(json.getString("rawId")) .setClientDataJSON(response.getString("clientDataJSON")) .setAttestationObject(response.getString("attestationObject")) .setTransports(typedTransports) + .setClientExtensionResults(extMap) .build()); } catch (JSONException e) { Log.e(TAG, "Error parsing response: " + resp, e); @@ -219,6 +261,7 @@ public void onError(CreateCredentialException e) { public void authenticate(@NonNull String relyingPartyId, @NonNull String challenge, @Nullable Long timeout, @Nullable String userVerification, @Nullable List allowCredentials, @Nullable Boolean preferImmediatelyAvailableCredentials, + @Nullable String salt, @NonNull Messages.Result result) { if (android.os.Build.VERSION.SDK_INT < 28) { result.error(new Messages.FlutterError("android-passkey-unsupported", @@ -235,8 +278,24 @@ public void authenticate(@NonNull String relyingPartyId, @NonNull String challen GetCredentialOptions getCredentialOptions = new GetCredentialOptions(challenge, timeout, relyingPartyId, allowCredentialsType, userVerification); try { - String options = getCredentialOptions.toJSON().toString(); - + JSONObject optionsJson = getCredentialOptions.toJSON(); + // PRF extension if salt provided + if (salt != null && !salt.isEmpty()) { + String saltBase64Url = hexToBase64Url(salt); + Log.i("Passkey", "salt provided auth" + saltBase64Url); + JSONObject extensions = optionsJson.optJSONObject("extensions"); + if (extensions == null) extensions = new JSONObject(); + + JSONObject prf = new JSONObject(); + JSONObject eval = new JSONObject(); + eval.put("first", saltBase64Url); + prf.put("eval", eval); + + extensions.put("prf", prf); + optionsJson.put("extensions", extensions); + } + String options = optionsJson.toString(); + Log.i("Passkeys", "options = " + options); Activity activity = plugin.requireActivity(); CredentialManager credentialManager = CredentialManager.create(activity); @@ -275,9 +334,30 @@ public void onResult(GetCredentialResponse res) { final String signature = response.getString("signature"); final String authenticatorData = response.getString("authenticatorData"); + JSONObject ext = json.optJSONObject("clientExtensionResults"); + Map extMap = null; + + if (ext != null) { + extMap = new HashMap<>(); + + JSONObject prf = ext.optJSONObject("prf"); + if (prf != null) { + JSONObject results = prf.optJSONObject("results"); + if (results != null) { + String first = results.optString("first", ""); + Map resultsMap = new HashMap<>(); + resultsMap.put("first", first); + Map prfMap = new HashMap<>(); + prfMap.put("results", resultsMap); + extMap.put("prf", prfMap); + } + } + } final Messages.AuthenticateResponse msg = new Messages.AuthenticateResponse.Builder() .setId(id).setRawId(rawId).setClientDataJSON(clientDataJSON) - .setAuthenticatorData(authenticatorData).setSignature(signature) + .setAuthenticatorData(authenticatorData) + .setSignature(signature) + .setClientExtensionResults(extMap) .setUserHandle(userHandle).build(); result.success(msg); @@ -339,4 +419,17 @@ public void cancelCurrentAuthenticatorOperation(@NonNull Messages.Result r result.success(null); } + + /// `hexToBase64Url` + public static String hexToBase64Url(String hex) { + int len = hex.length(); + byte[] bytes = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + bytes[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16); + } + return Base64.encodeToString( + bytes, + Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP + ); + } } diff --git a/packages/passkeys/passkeys_android/android/src/main/java/com/corbado/passkeys_android/Messages.java b/packages/passkeys/passkeys_android/android/src/main/java/com/corbado/passkeys_android/Messages.java index 2b5e2bd1..bd49c769 100644 --- a/packages/passkeys/passkeys_android/android/src/main/java/com/corbado/passkeys_android/Messages.java +++ b/packages/passkeys/passkeys_android/android/src/main/java/com/corbado/passkeys_android/Messages.java @@ -722,6 +722,17 @@ public void setTransports(@NonNull List setterArg) { this.transports = setterArg; } + /** The clientExtensionResults - PRF results */ + private @Nullable Map clientExtensionResults; + + public @Nullable Map getClientExtensionResults() { + return clientExtensionResults; + } + + public void setClientExtensionResults(@Nullable Map setterArg) { + this.clientExtensionResults = setterArg; + } + /** Constructor is non-public to enforce null safety; use Builder. */ RegisterResponse() {} @@ -762,6 +773,13 @@ public static final class Builder { return this; } + private @Nullable Map clientExtensionResults; + + public @NonNull Builder setClientExtensionResults(@Nullable Map setterArg) { + this.clientExtensionResults = setterArg; + return this; + } + public @NonNull RegisterResponse build() { RegisterResponse pigeonReturn = new RegisterResponse(); pigeonReturn.setId(id); @@ -769,18 +787,20 @@ public static final class Builder { pigeonReturn.setClientDataJSON(clientDataJSON); pigeonReturn.setAttestationObject(attestationObject); pigeonReturn.setTransports(transports); + pigeonReturn.setClientExtensionResults(clientExtensionResults); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(5); + ArrayList toListResult = new ArrayList(6); toListResult.add(id); toListResult.add(rawId); toListResult.add(clientDataJSON); toListResult.add(attestationObject); toListResult.add(transports); + toListResult.add(clientExtensionResults); return toListResult; } @@ -796,6 +816,8 @@ ArrayList toList() { pigeonResult.setAttestationObject((String) attestationObject); Object transports = list.get(4); pigeonResult.setTransports((List) transports); + Object clientExtensionResults = list.get(5); + pigeonResult.setClientExtensionResults((Map) clientExtensionResults); return pigeonResult; } } @@ -876,6 +898,7 @@ public void setSignature(@NonNull String setterArg) { this.signature = setterArg; } + /** The userHandle */ private @NonNull String userHandle; public @NonNull String getUserHandle() { @@ -889,6 +912,17 @@ public void setUserHandle(@NonNull String setterArg) { this.userHandle = setterArg; } + /** The clientExtensionResults - PRF results */ + private @Nullable Map clientExtensionResults; + + public @Nullable Map getClientExtensionResults() { + return clientExtensionResults; + } + + public void setClientExtensionResults(@Nullable Map setterArg) { + this.clientExtensionResults = setterArg; + } + /** Constructor is non-public to enforce null safety; use Builder. */ AuthenticateResponse() {} @@ -936,6 +970,13 @@ public static final class Builder { return this; } + private @Nullable Map clientExtensionResults; + + public @NonNull Builder setClientExtensionResults(@Nullable Map setterArg) { + this.clientExtensionResults = setterArg; + return this; + } + public @NonNull AuthenticateResponse build() { AuthenticateResponse pigeonReturn = new AuthenticateResponse(); pigeonReturn.setId(id); @@ -944,19 +985,21 @@ public static final class Builder { pigeonReturn.setAuthenticatorData(authenticatorData); pigeonReturn.setSignature(signature); pigeonReturn.setUserHandle(userHandle); + pigeonReturn.setClientExtensionResults(clientExtensionResults); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList(6); + ArrayList toListResult = new ArrayList(7); toListResult.add(id); toListResult.add(rawId); toListResult.add(clientDataJSON); toListResult.add(authenticatorData); toListResult.add(signature); toListResult.add(userHandle); + toListResult.add(clientExtensionResults); return toListResult; } @@ -974,6 +1017,8 @@ ArrayList toList() { pigeonResult.setSignature((String) signature); Object userHandle = list.get(5); pigeonResult.setUserHandle((String) userHandle); + Object clientExtensionResults = list.get(6); + pigeonResult.setClientExtensionResults((Map) clientExtensionResults); return pigeonResult; } } @@ -1053,9 +1098,9 @@ public interface PasskeysApi { void hasPasskeySupport(@NonNull Result result); - void register(@NonNull String challenge, @NonNull RelyingParty relyingParty, @NonNull User user, @Nullable AuthenticatorSelection authenticatorSelection, @Nullable List pubKeyCredParams, @Nullable Long timeout, @Nullable String attestation, @NonNull List excludeCredentials, @NonNull Result result); + void register(@NonNull String challenge, @NonNull RelyingParty relyingParty, @NonNull User user, @Nullable AuthenticatorSelection authenticatorSelection, @Nullable List pubKeyCredParams, @Nullable Long timeout, @Nullable String attestation, @NonNull List excludeCredentials, @Nullable String salt, @NonNull Result result); - void authenticate(@NonNull String relyingPartyId, @NonNull String challenge, @Nullable Long timeout, @Nullable String userVerification, @Nullable List allowCredentials, @Nullable Boolean preferImmediatelyAvailableCredentials, @NonNull Result result); + void authenticate(@NonNull String relyingPartyId, @NonNull String challenge, @Nullable Long timeout, @Nullable String userVerification, @Nullable List allowCredentials, @Nullable Boolean preferImmediatelyAvailableCredentials, @Nullable String salt, @NonNull Result result); void cancelCurrentAuthenticatorOperation(@NonNull Result result); @@ -1136,6 +1181,7 @@ public void error(Throwable error) { Number timeoutArg = (Number) args.get(5); String attestationArg = (String) args.get(6); List excludeCredentialsArg = (List) args.get(7); + String saltArg = (String) args.get(8); Result resultCallback = new Result() { public void success(RegisterResponse result) { @@ -1149,7 +1195,7 @@ public void error(Throwable error) { } }; - api.register(challengeArg, relyingPartyArg, userArg, authenticatorSelectionArg, pubKeyCredParamsArg, (timeoutArg == null) ? null : timeoutArg.longValue(), attestationArg, excludeCredentialsArg, resultCallback); + api.register(challengeArg, relyingPartyArg, userArg, authenticatorSelectionArg, pubKeyCredParamsArg, (timeoutArg == null) ? null : timeoutArg.longValue(), attestationArg, excludeCredentialsArg, saltArg, resultCallback); }); } else { channel.setMessageHandler(null); @@ -1170,6 +1216,7 @@ public void error(Throwable error) { String userVerificationArg = (String) args.get(3); List allowCredentialsArg = (List) args.get(4); Boolean preferImmediatelyAvailableCredentialsArg = (Boolean) args.get(5); + String saltArg = (String) args.get(6); Result resultCallback = new Result() { public void success(AuthenticateResponse result) { @@ -1183,7 +1230,7 @@ public void error(Throwable error) { } }; - api.authenticate(relyingPartyIdArg, challengeArg, (timeoutArg == null) ? null : timeoutArg.longValue(), userVerificationArg, allowCredentialsArg, preferImmediatelyAvailableCredentialsArg, resultCallback); + api.authenticate(relyingPartyIdArg, challengeArg, (timeoutArg == null) ? null : timeoutArg.longValue(), userVerificationArg, allowCredentialsArg, preferImmediatelyAvailableCredentialsArg, saltArg, resultCallback); }); } else { channel.setMessageHandler(null); diff --git a/packages/passkeys/passkeys_android/lib/messages.g.dart b/packages/passkeys/passkeys_android/lib/messages.g.dart index 7636bf51..9b3a68c3 100644 --- a/packages/passkeys/passkeys_android/lib/messages.g.dart +++ b/packages/passkeys/passkeys_android/lib/messages.g.dart @@ -219,6 +219,7 @@ class RegisterResponse { required this.clientDataJSON, required this.attestationObject, required this.transports, + this.clientExtensionResults, }); /// The ID @@ -236,6 +237,9 @@ class RegisterResponse { /// The supported transports for the authenticator List transports; + /// The clientExtensionResults - PRF results + Map? clientExtensionResults; + Object encode() { return [ id, @@ -243,6 +247,7 @@ class RegisterResponse { clientDataJSON, attestationObject, transports, + clientExtensionResults, ]; } @@ -254,6 +259,7 @@ class RegisterResponse { clientDataJSON: result[2]! as String, attestationObject: result[3]! as String, transports: (result[4] as List?)!.cast(), + clientExtensionResults: (result[5] as Map?)?.cast(), ); } } @@ -267,6 +273,7 @@ class AuthenticateResponse { required this.authenticatorData, required this.signature, required this.userHandle, + this.clientExtensionResults, }); /// The ID @@ -284,8 +291,12 @@ class AuthenticateResponse { /// The signature String signature; + /// The userHandle String userHandle; + /// The clientExtensionResults - PRF results + Map? clientExtensionResults; + Object encode() { return [ id, @@ -294,6 +305,7 @@ class AuthenticateResponse { authenticatorData, signature, userHandle, + clientExtensionResults, ]; } @@ -306,6 +318,7 @@ class AuthenticateResponse { authenticatorData: result[3]! as String, signature: result[4]! as String, userHandle: result[5]! as String, + clientExtensionResults: (result[6] as Map?)?.cast(), ); } } @@ -432,12 +445,12 @@ class PasskeysApi { } } - Future register(String arg_challenge, RelyingParty arg_relyingParty, User arg_user, AuthenticatorSelection? arg_authenticatorSelection, List? arg_pubKeyCredParams, int? arg_timeout, String? arg_attestation, List arg_excludeCredentials) async { + Future register(String arg_challenge, RelyingParty arg_relyingParty, User arg_user, AuthenticatorSelection? arg_authenticatorSelection, List? arg_pubKeyCredParams, int? arg_timeout, String? arg_attestation, List arg_excludeCredentials, String? arg_salt) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.passkeys_android.PasskeysApi.register', codec, binaryMessenger: _binaryMessenger); final List? replyList = - await channel.send([arg_challenge, arg_relyingParty, arg_user, arg_authenticatorSelection, arg_pubKeyCredParams, arg_timeout, arg_attestation, arg_excludeCredentials]) as List?; + await channel.send([arg_challenge, arg_relyingParty, arg_user, arg_authenticatorSelection, arg_pubKeyCredParams, arg_timeout, arg_attestation, arg_excludeCredentials, arg_salt]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -459,12 +472,12 @@ class PasskeysApi { } } - Future authenticate(String arg_relyingPartyId, String arg_challenge, int? arg_timeout, String? arg_userVerification, List? arg_allowCredentials, bool? arg_preferImmediatelyAvailableCredentials) async { + Future authenticate(String arg_relyingPartyId, String arg_challenge, int? arg_timeout, String? arg_userVerification, List? arg_allowCredentials, bool? arg_preferImmediatelyAvailableCredentials, String? arg_salt) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.passkeys_android.PasskeysApi.authenticate', codec, binaryMessenger: _binaryMessenger); final List? replyList = - await channel.send([arg_relyingPartyId, arg_challenge, arg_timeout, arg_userVerification, arg_allowCredentials, arg_preferImmediatelyAvailableCredentials]) as List?; + await channel.send([arg_relyingPartyId, arg_challenge, arg_timeout, arg_userVerification, arg_allowCredentials, arg_preferImmediatelyAvailableCredentials, arg_salt]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', diff --git a/packages/passkeys/passkeys_android/lib/passkeys_android.dart b/packages/passkeys/passkeys_android/lib/passkeys_android.dart index cf584ea6..a89864e0 100644 --- a/packages/passkeys/passkeys_android/lib/passkeys_android.dart +++ b/packages/passkeys/passkeys_android/lib/passkeys_android.dart @@ -16,9 +16,7 @@ class PasskeysAndroid extends PasskeysPlatform { final PasskeysApi _api; @override - Future authenticate( - AuthenticateRequestType request, - ) async { + Future authenticate(AuthenticateRequestType request, String? salt) async { final r = await _api.authenticate( request.relyingPartyId, request.challenge, @@ -28,10 +26,11 @@ class PasskeysAndroid extends PasskeysPlatform { return AllowCredential( id: e.id, type: e.type, - transports: e.transports, + transports: e.transports ?? [], ); }).toList(), request.preferImmediatelyAvailableCredentials, + salt, ); return AuthenticateResponseType( @@ -40,7 +39,8 @@ class PasskeysAndroid extends PasskeysPlatform { clientDataJSON: r.clientDataJSON, authenticatorData: r.authenticatorData, signature: r.signature, - userHandle: r.userHandle); + userHandle: r.userHandle, + clientExtensionResults: r.clientExtensionResults); } @override @@ -54,7 +54,7 @@ class PasskeysAndroid extends PasskeysPlatform { } @override - Future register(RegisterRequestType request) async { + Future register(RegisterRequestType request, String? salt) async { final userArg = User( displayName: request.user.displayName, name: request.user.name, @@ -85,14 +85,11 @@ class PasskeysAndroid extends PasskeysPlatform { relyingPartyArg, userArg, authSelection, - request.pubKeyCredParams - ?.map((e) => PubKeyCredParam(alg: e.alg, type: e.type)) - .toList(), + request.pubKeyCredParams?.map((e) => PubKeyCredParam(alg: e.alg, type: e.type)).toList(), request.timeout, request.attestation, - request.excludeCredentials - .map((e) => ExcludeCredential(id: e.id, type: e.type)) - .toList()); + request.excludeCredentials.map((e) => ExcludeCredential(id: e.id, type: e.type)).toList(), + salt); return RegisterResponseType( id: r.id, @@ -100,6 +97,7 @@ class PasskeysAndroid extends PasskeysPlatform { clientDataJSON: r.clientDataJSON, attestationObject: r.attestationObject, transports: r.transports.whereType().toList(), + clientExtensionResults: r.clientExtensionResults, ); } @@ -112,15 +110,13 @@ class PasskeysAndroid extends PasskeysPlatform { // In case of android we link passkey support to the availability of the biometric authentication @override Future getAvailability() async { - final isUserVerifyingPlatformAuthenticatorAvailable = - await canAuthenticate(); + final isUserVerifyingPlatformAuthenticatorAvailable = await canAuthenticate(); final hasPasskeySupport = await _api.hasPasskeySupport(); return AvailabilityTypeAndroid( hasPasskeySupport: hasPasskeySupport, - isUserVerifyingPlatformAuthenticatorAvailable: - isUserVerifyingPlatformAuthenticatorAvailable, + isUserVerifyingPlatformAuthenticatorAvailable: isUserVerifyingPlatformAuthenticatorAvailable, isNative: true, ); } diff --git a/packages/passkeys/passkeys_android/pigeons/messages.dart b/packages/passkeys/passkeys_android/pigeons/messages.dart index a2f251c5..ca774498 100644 --- a/packages/passkeys/passkeys_android/pigeons/messages.dart +++ b/packages/passkeys/passkeys_android/pigeons/messages.dart @@ -80,8 +80,7 @@ class ExcludeCredential { /// Represents an authenticator selection class AuthenticatorSelection { /// Constructor - const AuthenticatorSelection(this.authenticatorAttachment, - this.requireResidentKey, this.residentKey, this.userVerification); + const AuthenticatorSelection(this.authenticatorAttachment, this.requireResidentKey, this.residentKey, this.userVerification); /// The authenticator attachment final String? authenticatorAttachment; @@ -105,6 +104,7 @@ class RegisterResponse { required this.clientDataJSON, required this.attestationObject, required this.transports, + this.clientExtensionResults = const {}, }); /// The ID @@ -121,6 +121,9 @@ class RegisterResponse { /// The supported transports for the authenticator final List transports; + + /// The clientExtensionResults - PRF results + final Map? clientExtensionResults; } /// Represents an authenticate response @@ -133,6 +136,7 @@ class AuthenticateResponse { required this.authenticatorData, required this.signature, required this.userHandle, + this.clientExtensionResults = const {}, }); /// The ID @@ -150,7 +154,11 @@ class AuthenticateResponse { /// The signature final String signature; + /// The userHandle final String userHandle; + + /// The clientExtensionResults - PRF results + final Map? clientExtensionResults; } @HostApi() @@ -171,16 +179,19 @@ abstract class PasskeysApi { int? timeout, String? attestation, List excludeCredentials, + String? salt, ); @async AuthenticateResponse authenticate( - String relyingPartyId, - String challenge, - int? timeout, - String? userVerification, - List? allowCredentials, - bool? preferImmediatelyAvailableCredentials); + String relyingPartyId, + String challenge, + int? timeout, + String? userVerification, + List? allowCredentials, + bool? preferImmediatelyAvailableCredentials, + String? salt, + ); @async void cancelCurrentAuthenticatorOperation(); diff --git a/packages/passkeys/passkeys_android/pubspec.yaml b/packages/passkeys/passkeys_android/pubspec.yaml index 5924ff09..992c7413 100644 --- a/packages/passkeys/passkeys_android/pubspec.yaml +++ b/packages/passkeys/passkeys_android/pubspec.yaml @@ -3,6 +3,7 @@ description: Android implementation of the Corbado passkeys plugin. Manages the homepage: https://docs.corbado.com/overview/welcome repository: https://github.com/corbado/flutter-passkeys/tree/main/packages/passkeys/passkeys_android version: 2.11.0 +resolution: workspace environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/passkeys/passkeys_darwin/darwin/Classes/AuthenticateController.swift b/packages/passkeys/passkeys_darwin/darwin/Classes/AuthenticateController.swift index 118e8462..672931ea 100644 --- a/packages/passkeys/passkeys_darwin/darwin/Classes/AuthenticateController.swift +++ b/packages/passkeys/passkeys_darwin/darwin/Classes/AuthenticateController.swift @@ -63,13 +63,25 @@ class AuthenticateController: NSObject, ASAuthorizationControllerDelegate, ASAut completion?(.success(response)) break case let r as ASAuthorizationPlatformPublicKeyCredentialAssertion: + var prf: String? = nil + if #available(iOS 18.0, *), + let credPrf = r.prf { + let prfBytes = credPrf.first.withUnsafeBytes({ Data($0) }) + prf = prfBytes.base64EncodedString() + } + let response = AuthenticateResponse( id: r.credentialID.toBase64URL(), rawId: r.credentialID.toBase64URL(), clientDataJSON: r.rawClientDataJSON.toBase64URL(), authenticatorData: r.rawAuthenticatorData.toBase64URL(), signature: r.signature.toBase64URL(), - userHandle: r.userID?.toBase64URL() + userHandle: r.userID?.toBase64URL(), + clientExtensionResults: prf == nil ? nil : ["prf" : + ["enabled": true, + "results": ["first": prf] + ] + ] ) completion?(.success(response)) diff --git a/packages/passkeys/passkeys_darwin/darwin/Classes/PasskeysPlugin.swift b/packages/passkeys/passkeys_darwin/darwin/Classes/PasskeysPlugin.swift index 629907e4..058f30a1 100644 --- a/packages/passkeys/passkeys_darwin/darwin/Classes/PasskeysPlugin.swift +++ b/packages/passkeys/passkeys_darwin/darwin/Classes/PasskeysPlugin.swift @@ -53,6 +53,7 @@ public class PasskeysPlugin: NSObject, FlutterPlugin, PasskeysApi { canBeSecurityKey: Bool = true, residentKeyPreference: String?, attestationPreference: String?, + salt: String?, completion: @escaping (Result) -> Void ) { guard (try? canAuthenticate()) == true else { @@ -87,6 +88,15 @@ public class PasskeysPlugin: NSObject, FlutterPlugin, PasskeysApi { let excluded = parseCredentials(credentials: excludeCredentials) platformRequest.excludedCredentials = excluded } + + // PRF + if #available(iOS 18.0, *) { + guard let salt = salt, let saltData = Data(hex: salt) else { + return + } + let values = ASAuthorizationPublicKeyCredentialPRFAssertionInput.InputValues(saltInput1: saltData) + platformRequest.prf = ASAuthorizationPublicKeyCredentialPRFRegistrationInput.inputValues(values) + } requests.append(platformRequest) } @@ -154,6 +164,7 @@ public class PasskeysPlugin: NSObject, FlutterPlugin, PasskeysApi { conditionalUI: Bool, allowedCredentials: [CredentialType], preferImmediatelyAvailableCredentials: Bool, + salt: String?, completion: @escaping (Result) -> Void ) { guard (try? canAuthenticate()) == true else { @@ -171,6 +182,14 @@ public class PasskeysPlugin: NSObject, FlutterPlugin, PasskeysApi { let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: relyingPartyId) let platformRequest = platformProvider.createCredentialAssertionRequest(challenge: decodedChallenge) platformRequest.allowedCredentials = parseCredentials(credentials: allowedCredentials) + + // PRF + if #available(iOS 18.0, *) { + guard let salt = salt, let saltData = Data(hex: salt) else { return } + let values = ASAuthorizationPublicKeyCredentialPRFAssertionInput.InputValues(saltInput1: saltData) + platformRequest.prf = ASAuthorizationPublicKeyCredentialPRFAssertionInput.inputValues(values) + } + requests.append(platformRequest) // We should not show the security key flow when preferImmediatelyAvailable is set to true @@ -284,10 +303,38 @@ public extension Data { return fromBase64(base64String) } + func toBase64URL() -> String { + var result = self.base64EncodedString() + result = result.replacingOccurrences(of: "+", with: "-") + result = result.replacingOccurrences(of: "/", with: "_") + result = result.replacingOccurrences(of: "=", with: "") + return result + } + private static func base64UrlToBase64(base64Url: String) -> String { return base64Url.replacingOccurrences(of: "-", with: "+") .replacingOccurrences(of: "_", with: "/") } + + + /// init - https://stackoverflow.com/questions/26501276/converting-hex-string-to-nsdata-in-swift + init?(hex: String) { + let hex = hex.trimmingCharacters(in: .whitespacesAndNewlines) + guard hex.count % 2 == 0 else { return nil } + + var data = Data(capacity: hex.count / 2) + var index = hex.startIndex + + while index < hex.endIndex { + let nextIndex = hex.index(index, offsetBy: 2) + let byteString = hex[index.. String { - var result = self.base64EncodedString() - result = result.replacingOccurrences(of: "+", with: "-") - result = result.replacingOccurrences(of: "/", with: "_") - result = result.replacingOccurrences(of: "=", with: "") - return result - } -} diff --git a/packages/passkeys/passkeys_darwin/darwin/Classes/RegisterController.swift b/packages/passkeys/passkeys_darwin/darwin/Classes/RegisterController.swift index 5d4920b8..8e2cd649 100644 --- a/packages/passkeys/passkeys_darwin/darwin/Classes/RegisterController.swift +++ b/packages/passkeys/passkeys_darwin/darwin/Classes/RegisterController.swift @@ -37,13 +37,26 @@ class RegisterController: NSObject, ASAuthorizationControllerDelegate, ASAuthori func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { switch authorization.credential { case let credentialRegistration as ASAuthorizationPlatformPublicKeyCredentialRegistration: + var prf: String? = nil + if #available(iOS 18.0, *), + let credPrf = credentialRegistration.prf, + let prfBytes = credPrf.first?.withUnsafeBytes({ Data($0) }) { + prf = prfBytes.base64EncodedString() + } + let response = RegisterResponse( id: credentialRegistration.credentialID.toBase64URL(), rawId: credentialRegistration.credentialID.toBase64URL(), clientDataJSON: credentialRegistration.rawClientDataJSON.toBase64URL(), attestationObject: credentialRegistration.rawAttestationObject!.toBase64URL(), - transports: [] + transports: [], + clientExtensionResults: prf == nil ? nil : ["prf" : + ["enabled": true, + "results": ["first": prf] + ] + ] ) + completion?(.success(response)) break case let securityKeyRegistration as ASAuthorizationSecurityKeyPublicKeyCredentialRegistration: diff --git a/packages/passkeys/passkeys_darwin/darwin/Classes/messages.swift b/packages/passkeys/passkeys_darwin/darwin/Classes/messages.swift index 9b52cbd9..0f64dee8 100644 --- a/packages/passkeys/passkeys_darwin/darwin/Classes/messages.swift +++ b/packages/passkeys/passkeys_darwin/darwin/Classes/messages.swift @@ -135,6 +135,8 @@ struct RegisterResponse { var attestationObject: String /// The supported transports for the authenticator var transports: [String?] + /// The clientExtensionResults - PRF results + var clientExtensionResults: [String?: Any?]? = nil static func fromList(_ list: [Any?]) -> RegisterResponse? { let id = list[0] as! String @@ -142,13 +144,15 @@ struct RegisterResponse { let clientDataJSON = list[2] as! String let attestationObject = list[3] as! String let transports = list[4] as! [String?] + let clientExtensionResults: [String?: Any?]? = nilOrValue(list[5]) return RegisterResponse( id: id, rawId: rawId, clientDataJSON: clientDataJSON, attestationObject: attestationObject, - transports: transports + transports: transports, + clientExtensionResults: clientExtensionResults ) } func toList() -> [Any?] { @@ -158,6 +162,7 @@ struct RegisterResponse { clientDataJSON, attestationObject, transports, + clientExtensionResults, ] } } @@ -177,6 +182,8 @@ struct AuthenticateResponse { /// Signed challenge var signature: String var userHandle: String? = nil + /// The clientExtensionResults - PRF results + var clientExtensionResults: [String?: Any?]? = nil static func fromList(_ list: [Any?]) -> AuthenticateResponse? { let id = list[0] as! String @@ -185,6 +192,7 @@ struct AuthenticateResponse { let authenticatorData = list[3] as! String let signature = list[4] as! String let userHandle: String? = nilOrValue(list[5]) + let clientExtensionResults: [String?: Any?]? = nilOrValue(list[6]) return AuthenticateResponse( id: id, @@ -192,7 +200,8 @@ struct AuthenticateResponse { clientDataJSON: clientDataJSON, authenticatorData: authenticatorData, signature: signature, - userHandle: userHandle + userHandle: userHandle, + clientExtensionResults: clientExtensionResults ) } func toList() -> [Any?] { @@ -203,6 +212,7 @@ struct AuthenticateResponse { authenticatorData, signature, userHandle, + clientExtensionResults, ] } } @@ -267,8 +277,8 @@ class PasskeysApiCodec: FlutterStandardMessageCodec { protocol PasskeysApi { func canAuthenticate() throws -> Bool func hasBiometrics() throws -> Bool - func register(challenge: String, relyingParty: RelyingParty, user: User, excludeCredentials: [CredentialType], pubKeyCredValues: [Int64], canBePlatformAuthenticator: Bool, canBeSecurityKey: Bool, residentKeyPreference: String?, attestationPreference: String?, completion: @escaping (Result) -> Void) - func authenticate(relyingPartyId: String, challenge: String, conditionalUI: Bool, allowedCredentials: [CredentialType], preferImmediatelyAvailableCredentials: Bool, completion: @escaping (Result) -> Void) + func register(challenge: String, relyingParty: RelyingParty, user: User, excludeCredentials: [CredentialType], pubKeyCredValues: [Int64], canBePlatformAuthenticator: Bool, canBeSecurityKey: Bool, residentKeyPreference: String?, attestationPreference: String?, salt: String?, completion: @escaping (Result) -> Void) + func authenticate(relyingPartyId: String, challenge: String, conditionalUI: Bool, allowedCredentials: [CredentialType], preferImmediatelyAvailableCredentials: Bool, salt: String?, completion: @escaping (Result) -> Void) func cancelCurrentAuthenticatorOperation(completion: @escaping (Result) -> Void) } @@ -317,7 +327,8 @@ class PasskeysApiSetup { let canBeSecurityKeyArg = args[6] as! Bool let residentKeyPreferenceArg: String? = nilOrValue(args[7]) let attestationPreferenceArg: String? = nilOrValue(args[8]) - api.register(challenge: challengeArg, relyingParty: relyingPartyArg, user: userArg, excludeCredentials: excludeCredentialsArg, pubKeyCredValues: pubKeyCredValuesArg, canBePlatformAuthenticator: canBePlatformAuthenticatorArg, canBeSecurityKey: canBeSecurityKeyArg, residentKeyPreference: residentKeyPreferenceArg, attestationPreference: attestationPreferenceArg) { result in + let saltArg: String? = nilOrValue(args[9]) + api.register(challenge: challengeArg, relyingParty: relyingPartyArg, user: userArg, excludeCredentials: excludeCredentialsArg, pubKeyCredValues: pubKeyCredValuesArg, canBePlatformAuthenticator: canBePlatformAuthenticatorArg, canBeSecurityKey: canBeSecurityKeyArg, residentKeyPreference: residentKeyPreferenceArg, attestationPreference: attestationPreferenceArg, salt: saltArg) { result in switch result { case .success(let res): reply(wrapResult(res)) @@ -338,7 +349,8 @@ class PasskeysApiSetup { let conditionalUIArg = args[2] as! Bool let allowedCredentialsArg = args[3] as! [CredentialType] let preferImmediatelyAvailableCredentialsArg = args[4] as! Bool - api.authenticate(relyingPartyId: relyingPartyIdArg, challenge: challengeArg, conditionalUI: conditionalUIArg, allowedCredentials: allowedCredentialsArg, preferImmediatelyAvailableCredentials: preferImmediatelyAvailableCredentialsArg) { result in + let saltArg: String? = nilOrValue(args[5]) + api.authenticate(relyingPartyId: relyingPartyIdArg, challenge: challengeArg, conditionalUI: conditionalUIArg, allowedCredentials: allowedCredentialsArg, preferImmediatelyAvailableCredentials: preferImmediatelyAvailableCredentialsArg, salt: saltArg) { result in switch result { case .success(let res): reply(wrapResult(res)) diff --git a/packages/passkeys/passkeys_darwin/lib/messages.g.dart b/packages/passkeys/passkeys_darwin/lib/messages.g.dart index 1ed55e70..65fae640 100644 --- a/packages/passkeys/passkeys_darwin/lib/messages.g.dart +++ b/packages/passkeys/passkeys_darwin/lib/messages.g.dart @@ -109,6 +109,7 @@ class RegisterResponse { required this.clientDataJSON, required this.attestationObject, required this.transports, + this.clientExtensionResults, }); /// The ID @@ -126,6 +127,9 @@ class RegisterResponse { /// The supported transports for the authenticator List transports; + /// The clientExtensionResults - PRF results + Map? clientExtensionResults; + Object encode() { return [ id, @@ -133,6 +137,7 @@ class RegisterResponse { clientDataJSON, attestationObject, transports, + clientExtensionResults, ]; } @@ -144,6 +149,7 @@ class RegisterResponse { clientDataJSON: result[2]! as String, attestationObject: result[3]! as String, transports: (result[4] as List?)!.cast(), + clientExtensionResults: (result[5] as Map?)?.cast(), ); } } @@ -157,6 +163,7 @@ class AuthenticateResponse { required this.authenticatorData, required this.signature, this.userHandle, + this.clientExtensionResults, }); /// The ID @@ -176,6 +183,9 @@ class AuthenticateResponse { String? userHandle; + /// The clientExtensionResults - PRF results + Map? clientExtensionResults; + Object encode() { return [ id, @@ -184,6 +194,7 @@ class AuthenticateResponse { authenticatorData, signature, userHandle, + clientExtensionResults, ]; } @@ -196,6 +207,7 @@ class AuthenticateResponse { authenticatorData: result[3]! as String, signature: result[4]! as String, userHandle: result[5] as String?, + clientExtensionResults: (result[6] as Map?)?.cast(), ); } } @@ -307,12 +319,12 @@ class PasskeysApi { } } - Future register(String arg_challenge, RelyingParty arg_relyingParty, User arg_user, List arg_excludeCredentials, List arg_pubKeyCredValues, bool arg_canBePlatformAuthenticator, bool arg_canBeSecurityKey, String? arg_residentKeyPreference, String? arg_attestationPreference) async { + Future register(String arg_challenge, RelyingParty arg_relyingParty, User arg_user, List arg_excludeCredentials, List arg_pubKeyCredValues, bool arg_canBePlatformAuthenticator, bool arg_canBeSecurityKey, String? arg_residentKeyPreference, String? arg_attestationPreference, String? arg_salt) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.passkeys_darwin.PasskeysApi.register', codec, binaryMessenger: _binaryMessenger); final List? replyList = - await channel.send([arg_challenge, arg_relyingParty, arg_user, arg_excludeCredentials, arg_pubKeyCredValues, arg_canBePlatformAuthenticator, arg_canBeSecurityKey, arg_residentKeyPreference, arg_attestationPreference]) as List?; + await channel.send([arg_challenge, arg_relyingParty, arg_user, arg_excludeCredentials, arg_pubKeyCredValues, arg_canBePlatformAuthenticator, arg_canBeSecurityKey, arg_residentKeyPreference, arg_attestationPreference, arg_salt]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -334,12 +346,12 @@ class PasskeysApi { } } - Future authenticate(String arg_relyingPartyId, String arg_challenge, bool arg_conditionalUI, List arg_allowedCredentials, bool arg_preferImmediatelyAvailableCredentials) async { + Future authenticate(String arg_relyingPartyId, String arg_challenge, bool arg_conditionalUI, List arg_allowedCredentials, bool arg_preferImmediatelyAvailableCredentials, String? arg_salt) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.passkeys_darwin.PasskeysApi.authenticate', codec, binaryMessenger: _binaryMessenger); final List? replyList = - await channel.send([arg_relyingPartyId, arg_challenge, arg_conditionalUI, arg_allowedCredentials, arg_preferImmediatelyAvailableCredentials]) as List?; + await channel.send([arg_relyingPartyId, arg_challenge, arg_conditionalUI, arg_allowedCredentials, arg_preferImmediatelyAvailableCredentials, arg_salt]) as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', diff --git a/packages/passkeys/passkeys_darwin/lib/passkeys_darwin.dart b/packages/passkeys/passkeys_darwin/lib/passkeys_darwin.dart index d3b57e5a..4171ee44 100644 --- a/packages/passkeys/passkeys_darwin/lib/passkeys_darwin.dart +++ b/packages/passkeys/passkeys_darwin/lib/passkeys_darwin.dart @@ -21,7 +21,7 @@ class PasskeysDarwin extends PasskeysPlatform { Future canAuthenticate() async => _api.canAuthenticate(); @override - Future register(RegisterRequestType request) async { + Future register(RegisterRequestType request, String? salt) async { final userArg = User(name: request.user.name, id: request.user.id); final relyingPartyArg = RelyingParty( name: request.relyingParty.name, @@ -32,21 +32,17 @@ class PasskeysDarwin extends PasskeysPlatform { request.challenge, relyingPartyArg, userArg, - request.excludeCredentials - .map((e) => - CredentialType(type: e.type, id: e.id, transports: e.transports)) - .toList(), + request.excludeCredentials.map((e) => CredentialType(type: e.type, id: e.id, transports: e.transports ?? [])).toList(), request.pubKeyCredParams?.map((e) => e.alg).toList() ?? [], - request.authSelectionType == null || - request.authSelectionType!.authenticatorAttachment != - 'cross-platform', - request.authSelectionType == null || - request.authSelectionType!.authenticatorAttachment != 'platform', + request.authSelectionType == null || request.authSelectionType!.authenticatorAttachment != 'cross-platform', + request.authSelectionType == null || request.authSelectionType!.authenticatorAttachment != 'platform', request.authSelectionType?.residentKey, request.attestation, + salt, ); return RegisterResponseType( + clientExtensionResults: r.clientExtensionResults, id: r.id, rawId: r.rawId, clientDataJSON: r.clientDataJSON, @@ -56,9 +52,7 @@ class PasskeysDarwin extends PasskeysPlatform { } @override - Future authenticate( - AuthenticateRequestType request, - ) async { + Future authenticate(AuthenticateRequestType request, String? salt) async { var conditionalUI = false; if (request.mediation == MediationType.Conditional) { conditionalUI = true; @@ -68,12 +62,9 @@ class PasskeysDarwin extends PasskeysPlatform { request.relyingPartyId, request.challenge, conditionalUI, - request.allowCredentials - ?.map((e) => CredentialType( - type: e.type, id: e.id, transports: e.transports)) - .toList() ?? - [], + request.allowCredentials?.map((e) => CredentialType(type: e.type, id: e.id, transports: e.transports ?? [])).toList() ?? [], request.preferImmediatelyAvailableCredentials, + salt, ); return AuthenticateResponseType( @@ -83,12 +74,12 @@ class PasskeysDarwin extends PasskeysPlatform { authenticatorData: r.authenticatorData, signature: r.signature, userHandle: r.userHandle ?? '', + clientExtensionResults: r.clientExtensionResults, ); } @override - Future cancelCurrentAuthenticatorOperation() => - _api.cancelCurrentAuthenticatorOperation(); + Future cancelCurrentAuthenticatorOperation() => _api.cancelCurrentAuthenticatorOperation(); @override Future getAvailability() async { diff --git a/packages/passkeys/passkeys_darwin/pigeons/messages.dart b/packages/passkeys/passkeys_darwin/pigeons/messages.dart index af2dcd14..c6f87c3c 100644 --- a/packages/passkeys/passkeys_darwin/pigeons/messages.dart +++ b/packages/passkeys/passkeys_darwin/pigeons/messages.dart @@ -58,6 +58,7 @@ class RegisterResponse { required this.clientDataJSON, required this.attestationObject, required this.transports, + this.clientExtensionResults = const {}, }); /// The ID @@ -74,6 +75,9 @@ class RegisterResponse { /// The supported transports for the authenticator final List transports; + + /// The clientExtensionResults - PRF results + final Map? clientExtensionResults; } /// Represents an authenticate response @@ -86,6 +90,7 @@ class AuthenticateResponse { required this.authenticatorData, required this.signature, this.userHandle, + this.clientExtensionResults = const {}, }); /// The ID @@ -104,6 +109,9 @@ class AuthenticateResponse { final String signature; final String? userHandle; + + /// The clientExtensionResults - PRF results + final Map? clientExtensionResults; } @HostApi() @@ -123,6 +131,7 @@ abstract class PasskeysApi { bool canBeSecurityKey, String? residentKeyPreference, String? attestationPreference, + String? salt, ); @async @@ -132,6 +141,7 @@ abstract class PasskeysApi { bool conditionalUI, List allowedCredentials, bool preferImmediatelyAvailableCredentials, + String? salt, ); @async diff --git a/packages/passkeys/passkeys_platform_interface/lib/method_channel_passkeys.dart b/packages/passkeys/passkeys_platform_interface/lib/method_channel_passkeys.dart index 4c023184..42b6b8e7 100644 --- a/packages/passkeys/passkeys_platform_interface/lib/method_channel_passkeys.dart +++ b/packages/passkeys/passkeys_platform_interface/lib/method_channel_passkeys.dart @@ -8,16 +8,10 @@ class MethodChannelPasskeys extends PasskeysPlatform { Future canAuthenticate() async => throw UnimplementedError(); @override - Future register( - RegisterRequestType request, - ) async => - throw UnimplementedError(); + Future register(RegisterRequestType request, String? salt) async => throw UnimplementedError(); @override - Future authenticate( - AuthenticateRequestType request, - ) => - throw UnimplementedError(); + Future authenticate(AuthenticateRequestType request, String? salt) => throw UnimplementedError(); @override Future cancelCurrentAuthenticatorOperation() { diff --git a/packages/passkeys/passkeys_platform_interface/lib/passkeys_platform_interface.dart b/packages/passkeys/passkeys_platform_interface/lib/passkeys_platform_interface.dart index 5af29c95..8a52eb7c 100644 --- a/packages/passkeys/passkeys_platform_interface/lib/passkeys_platform_interface.dart +++ b/packages/passkeys/passkeys_platform_interface/lib/passkeys_platform_interface.dart @@ -40,14 +40,12 @@ abstract class PasskeysPlatform extends PlatformInterface { /// Handles the platform-specific steps for the registration flow /// (see https://webauthn.guide/#registration) /// Namely it creates a public/private key pair (only the public key will be returned) - Future register(RegisterRequestType request); + Future register(RegisterRequestType request, String? salt); /// Handles the platform-specific steps for the authentication flow /// (see https://webauthn.guide/#authentication) /// Namely it creates a signature for the challenge issued by the relying party - Future authenticate( - AuthenticateRequestType request, - ); + Future authenticate(AuthenticateRequestType request, String? salt); /// Cancels the ongoing authenticator operation (if there is one). /// This is important for the case when conditional UI has been started but diff --git a/packages/passkeys/passkeys_platform_interface/lib/types/authenticate_response.dart b/packages/passkeys/passkeys_platform_interface/lib/types/authenticate_response.dart index 4106eb25..dae0c195 100644 --- a/packages/passkeys/passkeys_platform_interface/lib/types/authenticate_response.dart +++ b/packages/passkeys/passkeys_platform_interface/lib/types/authenticate_response.dart @@ -2,13 +2,15 @@ import 'dart:convert'; class AuthenticateResponseType { /// Constructs a new instance. - const AuthenticateResponseType( - {required this.id, - required this.rawId, - required this.clientDataJSON, - required this.authenticatorData, - required this.signature, - required this.userHandle}); + const AuthenticateResponseType({ + required this.id, + required this.rawId, + required this.clientDataJSON, + required this.authenticatorData, + required this.signature, + required this.userHandle, + this.clientExtensionResults, + }); /// Constructs a new instance from a JSON string. factory AuthenticateResponseType.fromJsonString(String jsonString) { @@ -27,8 +29,7 @@ class AuthenticateResponseType { if (json.containsKey('response')) { final response = json['response']; if (response is! Map) { - throw FormatException( - 'Expected "response" to be a Map, got ${response.runtimeType}'); + throw FormatException('Expected "response" to be a Map, got ${response.runtimeType}'); } return AuthenticateResponseType( id: json['id'] as String? ?? '', @@ -37,6 +38,7 @@ class AuthenticateResponseType { authenticatorData: response['authenticatorData'] as String? ?? '', signature: response['signature'] as String? ?? '', userHandle: (response['userHandle'] as String?) ?? '', + clientExtensionResults: json['clientExtensionResults'] as Map?, ); } @@ -48,6 +50,7 @@ class AuthenticateResponseType { authenticatorData: json['authenticatorData'] as String? ?? '', signature: json['signature'] as String? ?? '', userHandle: (json['userHandle'] as String?) ?? '', + clientExtensionResults: json['clientExtensionResults'] as Map?, ); } @@ -69,6 +72,9 @@ class AuthenticateResponseType { /// The user handle. Can be empty if the user handle is not available. final String userHandle; + /// Optional client extension results. + final Map? clientExtensionResults; + /// Converts this instance to a JSON string. String toJsonString() => jsonEncode(toJson()); @@ -76,23 +82,28 @@ class AuthenticateResponseType { /// /// Output follows the standard WebAuthn format with nested 'response' object. Map toJson() { - final response = { - 'clientDataJSON': clientDataJSON, - 'authenticatorData': authenticatorData, - 'signature': signature, - }; + Map? sanitizedExtensions; + + final response = {'clientDataJSON': clientDataJSON, 'authenticatorData': authenticatorData, 'signature': signature}; // Only include userHandle if it's not empty if (userHandle.isNotEmpty) { response['userHandle'] = userHandle; } - return { - 'id': id, - 'rawId': rawId, - 'type': 'public-key', - 'response': response, - 'clientExtensionResults': {}, - }; + if (clientExtensionResults != null) { + sanitizedExtensions = {}; + + final prf = clientExtensionResults?['prf'] as Map?; + final results = prf?['results'] as Map?; + + if (results != null) { + sanitizedExtensions['prf'] = { + 'results': {'first': ''}, + }; + } + } + + return {'id': id, 'rawId': rawId, 'type': 'public-key', 'response': response, 'clientExtensionResults': sanitizedExtensions ?? {}}; } } diff --git a/packages/passkeys/passkeys_platform_interface/lib/types/authenticator_selection.g.dart b/packages/passkeys/passkeys_platform_interface/lib/types/authenticator_selection.g.dart index a7d11cad..1c3de652 100644 --- a/packages/passkeys/passkeys_platform_interface/lib/types/authenticator_selection.g.dart +++ b/packages/passkeys/passkeys_platform_interface/lib/types/authenticator_selection.g.dart @@ -7,20 +7,19 @@ part of 'authenticator_selection.dart'; // ************************************************************************** AuthenticatorSelectionType _$AuthenticatorSelectionTypeFromJson( - Map json) => - AuthenticatorSelectionType( - authenticatorAttachment: json['authenticatorAttachment'] as String?, - requireResidentKey: json['requireResidentKey'] as bool, - residentKey: json['residentKey'] as String, - userVerification: json['userVerification'] as String, - ); + Map json, +) => AuthenticatorSelectionType( + authenticatorAttachment: json['authenticatorAttachment'] as String?, + requireResidentKey: json['requireResidentKey'] as bool, + residentKey: json['residentKey'] as String, + userVerification: json['userVerification'] as String, +); Map _$AuthenticatorSelectionTypeToJson( - AuthenticatorSelectionType instance) => - { - if (instance.authenticatorAttachment case final value?) - 'authenticatorAttachment': value, - 'requireResidentKey': instance.requireResidentKey, - 'residentKey': instance.residentKey, - 'userVerification': instance.userVerification, - }; + AuthenticatorSelectionType instance, +) => { + 'authenticatorAttachment': ?instance.authenticatorAttachment, + 'requireResidentKey': instance.requireResidentKey, + 'residentKey': instance.residentKey, + 'userVerification': instance.userVerification, +}; diff --git a/packages/passkeys/passkeys_platform_interface/lib/types/credential.dart b/packages/passkeys/passkeys_platform_interface/lib/types/credential.dart index c35e2676..b1fea9c5 100644 --- a/packages/passkeys/passkeys_platform_interface/lib/types/credential.dart +++ b/packages/passkeys/passkeys_platform_interface/lib/types/credential.dart @@ -14,8 +14,7 @@ class CredentialType { }); /// Constructs a new instance from a JSON map. - factory CredentialType.fromJson(Map json) => - _$CredentialTypeFromJson(json); + factory CredentialType.fromJson(Map json) => _$CredentialTypeFromJson(json); /// The type of the credential. final String type; @@ -24,7 +23,7 @@ class CredentialType { final String id; /// The transports of the credential. - final List transports; + final List? transports; /// Converts this instance to a JSON map. Map toJson() => _$CredentialTypeToJson(this); diff --git a/packages/passkeys/passkeys_platform_interface/lib/types/credential.g.dart b/packages/passkeys/passkeys_platform_interface/lib/types/credential.g.dart index 5853222d..1875765f 100644 --- a/packages/passkeys/passkeys_platform_interface/lib/types/credential.g.dart +++ b/packages/passkeys/passkeys_platform_interface/lib/types/credential.g.dart @@ -10,8 +10,8 @@ CredentialType _$CredentialTypeFromJson(Map json) => CredentialType( type: json['type'] as String, id: json['id'] as String, - transports: (json['transports'] as List) - .map((e) => e as String) + transports: (json['transports'] as List?) + ?.map((e) => e as String) .toList(), ); diff --git a/packages/passkeys/passkeys_platform_interface/lib/types/pubkeycred_param.g.dart b/packages/passkeys/passkeys_platform_interface/lib/types/pubkeycred_param.g.dart index bbe65a03..7bd1101c 100644 --- a/packages/passkeys/passkeys_platform_interface/lib/types/pubkeycred_param.g.dart +++ b/packages/passkeys/passkeys_platform_interface/lib/types/pubkeycred_param.g.dart @@ -13,8 +13,5 @@ PubKeyCredParamType _$PubKeyCredParamTypeFromJson(Map json) => ); Map _$PubKeyCredParamTypeToJson( - PubKeyCredParamType instance) => - { - 'type': instance.type, - 'alg': instance.alg, - }; + PubKeyCredParamType instance, +) => {'type': instance.type, 'alg': instance.alg}; diff --git a/packages/passkeys/passkeys_platform_interface/lib/types/register_response.dart b/packages/passkeys/passkeys_platform_interface/lib/types/register_response.dart index b4e4a091..216359a3 100644 --- a/packages/passkeys/passkeys_platform_interface/lib/types/register_response.dart +++ b/packages/passkeys/passkeys_platform_interface/lib/types/register_response.dart @@ -7,6 +7,7 @@ class RegisterResponseType { required this.clientDataJSON, required this.attestationObject, required this.transports, + this.clientExtensionResults, }); /// Constructs a new instance from a JSON string. @@ -22,8 +23,7 @@ class RegisterResponseType { factory RegisterResponseType.fromJson(Map json) { final response = json['response']; if (response is! Map) { - throw FormatException( - 'Expected "response" to be a Map, got ${response.runtimeType}'); + throw FormatException('Expected "response" to be a Map, got ${response.runtimeType}'); } final transports = response['transports'] as List?; @@ -33,20 +33,23 @@ class RegisterResponseType { clientDataJSON: response['clientDataJSON'] as String? ?? '', attestationObject: response['attestationObject'] as String? ?? '', transports: transports?.map((e) => e as String?).toList() ?? [], + clientExtensionResults: json['clientExtensionResults'] as Map?, ); } - final String id; final String rawId; final String clientDataJSON; final String attestationObject; final List transports; + final Map? clientExtensionResults; /// Converts this instance to a JSON string. String toJsonString() => jsonEncode(toJson()); /// Converts this instance to a JSON map. Map toJson() { + Map? sanitizedExtensions; + final response = { 'clientDataJSON': clientDataJSON, 'attestationObject': attestationObject, @@ -58,12 +61,27 @@ class RegisterResponseType { response['transports'] = nonNullTransports; } + if (clientExtensionResults != null) { + sanitizedExtensions = {}; + + final prf = clientExtensionResults?['prf'] as Map?; + final results = prf?['results'] as Map?; + + if (results != null) { + sanitizedExtensions['prf'] = { + 'results': { + 'first': '', + } + }; + } + } + return { 'id': id, 'rawId': rawId, 'type': 'public-key', 'response': response, - 'clientExtensionResults': {}, + 'clientExtensionResults': sanitizedExtensions ?? {}, }; } } diff --git a/packages/passkeys/passkeys_platform_interface/lib/types/relying_party.g.dart b/packages/passkeys/passkeys_platform_interface/lib/types/relying_party.g.dart index 91e90dfa..57d3b6aa 100644 --- a/packages/passkeys/passkeys_platform_interface/lib/types/relying_party.g.dart +++ b/packages/passkeys/passkeys_platform_interface/lib/types/relying_party.g.dart @@ -7,13 +7,7 @@ part of 'relying_party.dart'; // ************************************************************************** RelyingPartyType _$RelyingPartyTypeFromJson(Map json) => - RelyingPartyType( - name: json['name'] as String, - id: json['id'] as String, - ); + RelyingPartyType(name: json['name'] as String, id: json['id'] as String); Map _$RelyingPartyTypeToJson(RelyingPartyType instance) => - { - 'name': instance.name, - 'id': instance.id, - }; + {'name': instance.name, 'id': instance.id}; diff --git a/packages/passkeys/passkeys_platform_interface/lib/types/user.g.dart b/packages/passkeys/passkeys_platform_interface/lib/types/user.g.dart index 856ceeb3..75cf7511 100644 --- a/packages/passkeys/passkeys_platform_interface/lib/types/user.g.dart +++ b/packages/passkeys/passkeys_platform_interface/lib/types/user.g.dart @@ -7,13 +7,13 @@ part of 'user.dart'; // ************************************************************************** UserType _$UserTypeFromJson(Map json) => UserType( - displayName: json['displayName'] as String, - name: json['name'] as String, - id: json['id'] as String, - ); + displayName: json['displayName'] as String, + name: json['name'] as String, + id: json['id'] as String, +); Map _$UserTypeToJson(UserType instance) => { - 'displayName': instance.displayName, - 'name': instance.name, - 'id': instance.id, - }; + 'displayName': instance.displayName, + 'name': instance.name, + 'id': instance.id, +}; diff --git a/packages/passkeys/passkeys_platform_interface/pubspec.yaml b/packages/passkeys/passkeys_platform_interface/pubspec.yaml index ab113204..60274a2c 100644 --- a/packages/passkeys/passkeys_platform_interface/pubspec.yaml +++ b/packages/passkeys/passkeys_platform_interface/pubspec.yaml @@ -5,8 +5,7 @@ repository: https://github.com/corbado/flutter-passkeys/tree/main/packages/passk version: 2.6.0 environment: - sdk: ">=3.0.0 <4.0.0" - + sdk: '>=3.8.0 <4.0.0' dependencies: flutter: sdk: flutter diff --git a/packages/passkeys/passkeys_windows/lib/messages.g.dart b/packages/passkeys/passkeys_windows/lib/messages.g.dart index 99ce0f23..8d68c6bf 100644 --- a/packages/passkeys/passkeys_windows/lib/messages.g.dart +++ b/packages/passkeys/passkeys_windows/lib/messages.g.dart @@ -374,17 +374,14 @@ class PasskeysApi { /// Constructor for [PasskeysApi]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - PasskeysApi({BinaryMessenger? binaryMessenger}) - : _binaryMessenger = binaryMessenger; + PasskeysApi({BinaryMessenger? binaryMessenger}) : _binaryMessenger = binaryMessenger; final BinaryMessenger? _binaryMessenger; static const MessageCodec codec = _PasskeysApiCodec(); Future canAuthenticate() async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.passkeys_windows.PasskeysApi.canAuthenticate', - codec, - binaryMessenger: _binaryMessenger); + final BasicMessageChannel channel = + BasicMessageChannel('dev.flutter.pigeon.passkeys_windows.PasskeysApi.canAuthenticate', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send(null) as List?; if (replyList == null) { throw PlatformException( @@ -408,10 +405,8 @@ class PasskeysApi { } Future hasPasskeySupport() async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.passkeys_windows.PasskeysApi.hasPasskeySupport', - codec, - binaryMessenger: _binaryMessenger); + final BasicMessageChannel channel = + BasicMessageChannel('dev.flutter.pigeon.passkeys_windows.PasskeysApi.hasPasskeySupport', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send(null) as List?; if (replyList == null) { throw PlatformException( @@ -434,18 +429,10 @@ class PasskeysApi { } } - Future register( - String arg_challenge, - RelyingParty arg_relyingParty, - User arg_user, - AuthenticatorSelection? arg_authenticatorSelection, - List? arg_pubKeyCredParams, - int? arg_timeout, - String? arg_attestation, - List arg_excludeCredentials) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.passkeys_windows.PasskeysApi.register', codec, - binaryMessenger: _binaryMessenger); + Future register(String arg_challenge, RelyingParty arg_relyingParty, User arg_user, AuthenticatorSelection? arg_authenticatorSelection, + List? arg_pubKeyCredParams, int? arg_timeout, String? arg_attestation, List arg_excludeCredentials) async { + final BasicMessageChannel channel = + BasicMessageChannel('dev.flutter.pigeon.passkeys_windows.PasskeysApi.register', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send([ arg_challenge, arg_relyingParty, @@ -477,24 +464,13 @@ class PasskeysApi { } } - Future authenticate( - String arg_relyingPartyId, - String arg_challenge, - int? arg_timeout, - String? arg_userVerification, - List? arg_allowCredentials, - bool? arg_preferImmediatelyAvailableCredentials) async { - final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.passkeys_windows.PasskeysApi.authenticate', codec, - binaryMessenger: _binaryMessenger); - final List? replyList = await channel.send([ - arg_relyingPartyId, - arg_challenge, - arg_timeout, - arg_userVerification, - arg_allowCredentials, - arg_preferImmediatelyAvailableCredentials - ]) as List?; + Future authenticate(String arg_relyingPartyId, String arg_challenge, int? arg_timeout, String? arg_userVerification, + List? arg_allowCredentials, bool? arg_preferImmediatelyAvailableCredentials) async { + final BasicMessageChannel channel = + BasicMessageChannel('dev.flutter.pigeon.passkeys_windows.PasskeysApi.authenticate', codec, binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send( + [arg_relyingPartyId, arg_challenge, arg_timeout, arg_userVerification, arg_allowCredentials, arg_preferImmediatelyAvailableCredentials]) + as List?; if (replyList == null) { throw PlatformException( code: 'channel-error', @@ -518,8 +494,7 @@ class PasskeysApi { Future cancelCurrentAuthenticatorOperation() async { final BasicMessageChannel channel = BasicMessageChannel( - 'dev.flutter.pigeon.passkeys_windows.PasskeysApi.cancelCurrentAuthenticatorOperation', - codec, + 'dev.flutter.pigeon.passkeys_windows.PasskeysApi.cancelCurrentAuthenticatorOperation', codec, binaryMessenger: _binaryMessenger); final List? replyList = await channel.send(null) as List?; if (replyList == null) { diff --git a/packages/passkeys/passkeys_windows/lib/passkeys_windows.dart b/packages/passkeys/passkeys_windows/lib/passkeys_windows.dart index f528795c..fb52350a 100644 --- a/packages/passkeys/passkeys_windows/lib/passkeys_windows.dart +++ b/packages/passkeys/passkeys_windows/lib/passkeys_windows.dart @@ -16,9 +16,7 @@ class PasskeysWindows extends PasskeysPlatform { final PasskeysApi _api; @override - Future authenticate( - AuthenticateRequestType request, - ) async { + Future authenticate(AuthenticateRequestType request, String? salt) async { final authenticateResponse = await _api.authenticate( request.relyingPartyId, request.challenge, @@ -29,7 +27,7 @@ class PasskeysWindows extends PasskeysPlatform { (e) => AllowCredential( type: e.type, id: e.id, - transports: e.transports, + transports: e.transports ?? [], ), ) .toList(), @@ -57,7 +55,7 @@ class PasskeysWindows extends PasskeysPlatform { } @override - Future register(RegisterRequestType request) async { + Future register(RegisterRequestType request, String? salt) async { final userArg = User( displayName: request.user.displayName, name: request.user.name, @@ -74,8 +72,7 @@ class PasskeysWindows extends PasskeysPlatform { if (requestAuthSelectionType != null) { authSelection = AuthenticatorSelection( - authenticatorAttachment: - requestAuthSelectionType.authenticatorAttachment, + authenticatorAttachment: requestAuthSelectionType.authenticatorAttachment, requireResidentKey: requestAuthSelectionType.requireResidentKey, residentKey: requestAuthSelectionType.residentKey, userVerification: requestAuthSelectionType.userVerification, @@ -87,14 +84,10 @@ class PasskeysWindows extends PasskeysPlatform { relyingPartyArg, userArg, authSelection, - request.pubKeyCredParams - ?.map((e) => PubKeyCredParam(type: e.type, alg: e.alg)) - .toList(), + request.pubKeyCredParams?.map((e) => PubKeyCredParam(type: e.type, alg: e.alg)).toList(), request.timeout, request.attestation, - request.excludeCredentials - .map((e) => ExcludeCredential(type: e.type, id: e.id)) - .toList(), + request.excludeCredentials.map((e) => ExcludeCredential(type: e.type, id: e.id)).toList(), ); return RegisterResponseType( @@ -107,20 +100,17 @@ class PasskeysWindows extends PasskeysPlatform { } @override - Future cancelCurrentAuthenticatorOperation() => - _api.cancelCurrentAuthenticatorOperation(); + Future cancelCurrentAuthenticatorOperation() => _api.cancelCurrentAuthenticatorOperation(); @override Future getAvailability() async { - final isUserVerifyingPlatformAuthenticatorAvailable = - await canAuthenticate(); + final isUserVerifyingPlatformAuthenticatorAvailable = await canAuthenticate(); final hasPasskeySupport = await _api.hasPasskeySupport(); return AvailabilityTypeWindows( hasPasskeySupport: hasPasskeySupport, - isUserVerifyingPlatformAuthenticatorAvailable: - isUserVerifyingPlatformAuthenticatorAvailable, + isUserVerifyingPlatformAuthenticatorAvailable: isUserVerifyingPlatformAuthenticatorAvailable, isNative: true, ); }