diff --git a/android/src/main/kotlin/net/archethic/yubikit_android/methods/Connect.kt b/android/src/main/kotlin/net/archethic/yubikit_android/methods/Connect.kt new file mode 100644 index 0000000..9a53d0b --- /dev/null +++ b/android/src/main/kotlin/net/archethic/yubikit_android/methods/Connect.kt @@ -0,0 +1,10 @@ +package net.archethic.yubikit_android.methods + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +class Connect : MethodHandler { + override fun handle(call: MethodCall, result: MethodChannel.Result) { + + } +} diff --git a/android/src/main/kotlin/net/archethic/yubikit_android/methods/MethodHandler.kt b/android/src/main/kotlin/net/archethic/yubikit_android/methods/MethodHandler.kt new file mode 100644 index 0000000..fec517d --- /dev/null +++ b/android/src/main/kotlin/net/archethic/yubikit_android/methods/MethodHandler.kt @@ -0,0 +1,9 @@ +package net.archethic.yubikit_android.methods + +import androidx.annotation.NonNull +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +interface MethodHandler { + fun handle(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result); +} diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index e7c9182..0969b27 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -36,4 +36,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.0 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 1bb71bf..2d12c70 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -205,6 +205,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -241,6 +242,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); diff --git a/example/lib/components/capabilities_text.dart b/example/lib/components/capabilities_text.dart index d38f1c9..f6612b1 100644 --- a/example/lib/components/capabilities_text.dart +++ b/example/lib/components/capabilities_text.dart @@ -24,7 +24,7 @@ class CapabilitiesText extends StatelessWidget { Future capabilitiesString() async { try { - final capabilities = await yubikitPlugin.general.deviceCapabilities; + final capabilities = await yubikitPlugin.connection.deviceCapabilities; return 'nfc : ${capabilities.nfc}, wired : ${capabilities.wired}'; } on PlatformException { return 'Failed to get device capabilities'; diff --git a/example/lib/components/generate_key_button.dart b/example/lib/components/generate_key_button.dart index e5502a0..c5051a7 100644 --- a/example/lib/components/generate_key_button.dart +++ b/example/lib/components/generate_key_button.dart @@ -14,12 +14,20 @@ class GenerateKeyButton extends StatelessWidget { Widget build(BuildContext context) => ActionButton( text: 'Generate key', onPressed: () async { - final publicKey = await yubikitPlugin.piv.generateKey( - pin: "123456", - managementKey: PivManagementKey.fromString( + final connection = await yubikitPlugin.connection.connect( + timeout: const Duration(seconds: 15), + ); + final piv = await connection.pivSession; + + await piv.verifyPin("123456"); + await piv.authenticate( + PivManagementKey.fromString( "010203040506070801020304050607080102030405060708", keyType: PivManagementKeyType.tripleDES, ), + ); + + final publicKey = await piv.generateKey( pinPolicy: PivPinPolicy.defaultPolicy, type: PivKeyType.eccp256, touchPolicy: PivTouchPolicy.defaultPolicy, diff --git a/example/lib/components/piv_calculate_secret_button.dart b/example/lib/components/piv_calculate_secret_button.dart index a42f69a..d7f38ca 100644 --- a/example/lib/components/piv_calculate_secret_button.dart +++ b/example/lib/components/piv_calculate_secret_button.dart @@ -14,9 +14,13 @@ class PivCalculateSecretButton extends StatelessWidget { Widget build(BuildContext context) => ActionButton( text: 'Calculate secret', onPressed: () async { - final secret = await yubikitPlugin.piv.calculateSecret( + final connection = await yubikitPlugin.connection.connect(); + final piv = await connection.pivSession; + + piv.verifyPin("123456"); + + final secret = await piv.calculateSecret( slot: PivSlot.authentication, - pin: "123456", peerPublicKey: """ -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElqeFrBCjtonol5ksKYCuXf+alUTI diff --git a/example/lib/components/piv_read_cert_button.dart b/example/lib/components/piv_read_cert_button.dart index c38eb27..91c8c36 100644 --- a/example/lib/components/piv_read_cert_button.dart +++ b/example/lib/components/piv_read_cert_button.dart @@ -14,11 +14,13 @@ class PivReadCertButton extends StatelessWidget { Widget build(BuildContext context) => ActionButton( text: 'Read certificate', onPressed: () async { - final certificate = await yubikitPlugin.piv.getCertificate( - pin: "123456", + final connection = await yubikitPlugin.connection.connect(); + final piv = await connection.pivSession; + await piv.verifyPin("123456"); + final publicKey = await piv.getCertificate( slot: PivSlot.signature, ); - return String.fromCharCodes(certificate); + return String.fromCharCodes(publicKey); }, ); } diff --git a/ios/Classes/handlers/PivCalculateSecret.swift b/ios/Classes/handlers/PivCalculateSecret.swift index d75a256..908d18f 100644 --- a/ios/Classes/handlers/PivCalculateSecret.swift +++ b/ios/Classes/handlers/PivCalculateSecret.swift @@ -56,7 +56,6 @@ class PivCalculateSecretHandler: Handler { return } - pivSession.verifyPin(pin) { retries, verifyPinError in guard verifyPinError == nil else { context.failure( diff --git a/lib/src/domain/protocol/connection/protocol.dart b/lib/src/domain/protocol/connection/protocol.dart new file mode 100644 index 0000000..d803340 --- /dev/null +++ b/lib/src/domain/protocol/connection/protocol.dart @@ -0,0 +1,18 @@ +import 'package:yubidart/yubidart.dart'; + +abstract class Connection { + Future get pivSession; + + Future get otpSession; +} + +abstract class ConnectionProtocol { + /// Looks at the device capabilities (connectivity mainly) + Future get deviceCapabilities; + + Future connect({ + Duration timeout = const Duration(minutes: 1), + }); + + Future disconnect(); +} diff --git a/lib/src/domain/protocol/general/protocol.dart b/lib/src/domain/protocol/general/protocol.dart deleted file mode 100644 index bad1961..0000000 --- a/lib/src/domain/protocol/general/protocol.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:yubidart/src/domain/model/general/device_capabilities.dart'; - -abstract class GeneralProtocol { - /// Looks at the device capabilities (connectivity mainly) - Future get deviceCapabilities; -} diff --git a/lib/src/domain/protocol/piv/protocol.dart b/lib/src/domain/protocol/piv/protocol.dart index 6cdd3b5..0526d20 100644 --- a/lib/src/domain/protocol/piv/protocol.dart +++ b/lib/src/domain/protocol/piv/protocol.dart @@ -6,6 +6,16 @@ import 'package:yubidart/src/domain/model/piv/slot.dart'; import 'package:yubidart/src/domain/model/piv/touch_policy.dart'; abstract class PivProtocol { + /// Verifies the PIN code. + /// + /// [pin] The pin. Default pin code is 123456. + Future verifyPin(String pin); + + /// Authenticates with the management key + /// + /// [managementKey] The management key. Default value is 000102030405060708090A0B0C0D0E0F1011121314151617. + Future authenticate(PivManagementKey managementKey); + /// Generates a new key pair within the YubiKey. /// This method requires authentication and pin verification. /// @@ -16,8 +26,6 @@ abstract class PivProtocol { /// TouchPolicy.CACHED requires support for touch cached, available on YubiKey 4.3 or later. /// This method is thread safe and can be invoked from any thread (main or a background thread). /// - /// [pin] The pin. Default pin code is 123456. - /// [managementKey] The management key. Default is 010203040506070801020304050607080102030405060708. /// [slot] The slot to generate the new key in. /// [type] Which algorithm is used for key generation. /// [pinPolicy] The PIN policy for using the private key. @@ -27,8 +35,6 @@ abstract class PivProtocol { /// /// Throws a YKFailure Future generateKey({ - required String pin, - required PivManagementKey managementKey, required PivSlot slot, required PivKeyType type, required PivPinPolicy pinPolicy, @@ -37,20 +43,17 @@ abstract class PivProtocol { /// Reads the X.509 certificate stored in the specified slot on the YubiKey. /// - /// [pin] The pin. Default pin code is 123456. /// [slot] : The slot where the certificate is stored. /// /// Returns certificate instance /// /// Throws a YKFailure Future getCertificate({ - required String pin, required PivSlot slot, }); /// Perform an ECDH operation with a given public key to compute a shared secret. /// - /// [pin] The pin. Default pin code is 123456. /// [slot] The slot containing the private EC key to use. /// [peerPublicKey] The peer public key for the operation. This is an EllipticCurve encryption public key in PEM format. /// @@ -58,7 +61,6 @@ abstract class PivProtocol { /// /// Throws a YKFailure Future calculateSecret({ - required String pin, required PivSlot slot, required String peerPublicKey, }); diff --git a/lib/src/domain/protocol/protocol.dart b/lib/src/domain/protocol/protocol.dart index e0ad23a..78f6e49 100644 --- a/lib/src/domain/protocol/protocol.dart +++ b/lib/src/domain/protocol/protocol.dart @@ -1,3 +1,3 @@ -export 'general/protocol.dart'; +export 'connection/protocol.dart'; export 'otp/otp.dart'; export 'piv/protocol.dart'; diff --git a/lib/src/domain/yubidart_platform_interface.dart b/lib/src/domain/yubidart_platform_interface.dart index 20ccdd1..d9d7fe3 100644 --- a/lib/src/domain/yubidart_platform_interface.dart +++ b/lib/src/domain/yubidart_platform_interface.dart @@ -1,6 +1,5 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart'; -import 'package:yubidart/src/domain/protocol/general/protocol.dart'; -import 'package:yubidart/src/domain/protocol/piv/protocol.dart'; +import 'package:yubidart/src/domain/protocol/connection/protocol.dart'; abstract class YubidartPlatform extends PlatformInterface { /// Constructs a [YubidartPlatform]. @@ -15,9 +14,7 @@ abstract class YubidartPlatform extends PlatformInterface { /// Defaults to MethodChannelYubidart. static YubidartPlatform get instance => _instance; - PivProtocol get piv; - - GeneralProtocol get general; + ConnectionProtocol get connection; /// Platform-specific implementations should set this with their own /// platform-specific class that extends [YubidartPlatform] when @@ -30,8 +27,5 @@ abstract class YubidartPlatform extends PlatformInterface { class EmptyYubidartPlatformImplementation implements YubidartPlatform { @override - GeneralProtocol get general => throw UnimplementedError(); - - @override - PivProtocol get piv => throw UnimplementedError(); + ConnectionProtocol get connection => throw UnimplementedError(); } diff --git a/lib/src/infrastructure/protocol/connection/default_connection_protocol.dart b/lib/src/infrastructure/protocol/connection/default_connection_protocol.dart new file mode 100644 index 0000000..cc05e73 --- /dev/null +++ b/lib/src/infrastructure/protocol/connection/default_connection_protocol.dart @@ -0,0 +1,72 @@ +import 'package:flutter/services.dart'; +import 'package:yubidart/src/domain/model/failure/failure.dart'; +import 'package:yubidart/src/domain/model/general/device_capabilities.dart'; +import 'package:yubidart/src/domain/protocol/connection/protocol.dart'; +import 'package:yubidart/src/domain/protocol/otp/otp.dart'; +import 'package:yubidart/src/domain/protocol/piv/protocol.dart'; +import 'package:yubidart/src/infrastructure/protocol/otp/default_otp_protocol.dart'; +import 'package:yubidart/src/infrastructure/protocol/otp/yubicloud_client.dart'; +import 'package:yubidart/src/infrastructure/protocol/piv/default_piv_protocol.dart'; + +class DefaultConnection implements Connection { + @override + Future get pivSession async => DefaultPivProtocol(); + + @override + Future get otpSession async => + DefaultOTPProtocol(yubicloudClient: YubicloudClient()); +} + +class DefaultConnectionProtocol implements ConnectionProtocol { + /// The method channel used to interact with the native platform. + // @foundation.visibleForTesting + final methodChannel = const MethodChannel('net.archethic/yubidart'); + + @override + Future get deviceCapabilities => YKFailure.guard( + () async { + final supportsNFCScanning = + await methodChannel.invokeMethod('supportsNFCScanning'); + final supportsISO7816NFCTags = + await methodChannel.invokeMethod('supportsISO7816NFCTags'); + final supportsMFIAccessoryKey = + await methodChannel.invokeMethod('supportsMFIAccessoryKey'); + + if (supportsNFCScanning == null || + supportsISO7816NFCTags == null || + supportsMFIAccessoryKey == null) { + throw YKFailure.other(); + } + + return DeviceCapabilities( + nfc: supportsNFCScanning || supportsISO7816NFCTags, + wired: supportsMFIAccessoryKey, + ); + }, + ); + + @override + Future connect({ + Duration timeout = const Duration(minutes: 1), + }) { + return YKFailure.guard( + () async { + await methodChannel.invokeMethod( + 'connect', + ); + return DefaultConnection(); + }, + ); + } + + @override + Future disconnect() { + return YKFailure.guard( + () async { + await methodChannel.invokeMethod( + 'disconnect', + ); + }, + ); + } +} diff --git a/lib/src/infrastructure/protocol/general/default_general_protocol.dart b/lib/src/infrastructure/protocol/general/default_general_protocol.dart deleted file mode 100644 index e289c4e..0000000 --- a/lib/src/infrastructure/protocol/general/default_general_protocol.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:yubidart/src/domain/model/model.dart'; -import 'package:yubidart/src/domain/protocol/general/protocol.dart'; - -class DefaultGeneralProtocol implements GeneralProtocol { - /// The method channel used to interact with the native platform. - // @foundation.visibleForTesting - final methodChannel = const MethodChannel('net.archethic/yubidart'); - - @override - Future get deviceCapabilities => YKFailure.guard( - () async { - final supportsNFCScanning = - await methodChannel.invokeMethod('supportsNFCScanning'); - final supportsISO7816NFCTags = - await methodChannel.invokeMethod('supportsISO7816NFCTags'); - final supportsMFIAccessoryKey = - await methodChannel.invokeMethod('supportsMFIAccessoryKey'); - - if (supportsNFCScanning == null || - supportsISO7816NFCTags == null || - supportsMFIAccessoryKey == null) { - throw YKFailure.other(); - } - - return DeviceCapabilities( - nfc: supportsNFCScanning || supportsISO7816NFCTags, - wired: supportsMFIAccessoryKey, - ); - }, - ); -} diff --git a/lib/src/infrastructure/protocol/piv/default_piv_protocol.dart b/lib/src/infrastructure/protocol/piv/default_piv_protocol.dart index 36fce3c..6ca556c 100644 --- a/lib/src/infrastructure/protocol/piv/default_piv_protocol.dart +++ b/lib/src/infrastructure/protocol/piv/default_piv_protocol.dart @@ -14,8 +14,6 @@ class DefaultPivProtocol implements PivProtocol { @override Future generateKey({ - required String pin, - required PivManagementKey managementKey, required PivSlot slot, required PivKeyType type, required PivPinPolicy pinPolicy, @@ -26,9 +24,6 @@ class DefaultPivProtocol implements PivProtocol { final result = await methodChannel.invokeMethod( 'pivGenerateKey', { - 'pin': pin, - 'managementKey': managementKey.key, - 'managementKeyType': managementKey.keyType.value, 'slot': slot.value, 'type': type.value, 'pinPolicy': pinPolicy.value, @@ -44,7 +39,6 @@ class DefaultPivProtocol implements PivProtocol { @override Future getCertificate({ - required String pin, required PivSlot slot, }) => YKFailure.guard( @@ -52,7 +46,6 @@ class DefaultPivProtocol implements PivProtocol { final result = await methodChannel.invokeMethod( 'pivGetCertificate', { - 'pin': pin, 'slot': slot.value, }, ); @@ -67,14 +60,12 @@ class DefaultPivProtocol implements PivProtocol { @override Future calculateSecret({ required PivSlot slot, - required String pin, required String peerPublicKey, }) async { final result = await methodChannel.invokeMethod( 'pivCalculateSecret', { 'slot': slot.value, - 'pin': pin, 'peerPublicKey': Uint8List.fromList( PemCodec(PemLabel.publicKey).decode(peerPublicKey), ), @@ -85,4 +76,33 @@ class DefaultPivProtocol implements PivProtocol { } return result; } + + @override + Future authenticate(PivManagementKey managementKey) async { + final result = await methodChannel.invokeMethod( + 'pivAuthenticate', + { + 'managementKey': managementKey.key, + 'managementKeyType': managementKey.keyType.value, + }, + ); + if (result == null) { + throw YKFailure.other(); + } + return this; + } + + @override + Future verifyPin(String pin) async { + final result = await methodChannel.invokeMethod( + 'pivVerifyPin', + { + 'pin': pin, + }, + ); + if (result == null) { + throw YKFailure.other(); + } + return this; + } } diff --git a/lib/src/infrastructure/yubidart_android.dart b/lib/src/infrastructure/yubidart_android.dart index ce9ba0b..731458e 100644 --- a/lib/src/infrastructure/yubidart_android.dart +++ b/lib/src/infrastructure/yubidart_android.dart @@ -1,10 +1,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:yubidart/src/domain/model/general/device_capabilities.dart'; -import 'package:yubidart/src/domain/protocol/general/protocol.dart'; -import 'package:yubidart/src/domain/protocol/piv/protocol.dart'; +import 'package:yubidart/src/domain/protocol/connection/protocol.dart'; import 'package:yubidart/src/domain/yubidart_platform_interface.dart'; -import 'package:yubidart/src/infrastructure/protocol/piv/default_piv_protocol.dart'; +import 'package:yubidart/src/infrastructure/protocol/connection/default_connection_protocol.dart'; /// An implementation of [YubidartPlatform] for Android. class YubidartAndroid extends YubidartPlatform { @@ -17,17 +15,5 @@ class YubidartAndroid extends YubidartPlatform { } @override - GeneralProtocol get general => DumbGeneralProtocol(); - - @override - PivProtocol get piv => DefaultPivProtocol(); -} - -class DumbGeneralProtocol implements GeneralProtocol { - @override - Future get deviceCapabilities async => - const DeviceCapabilities( - nfc: true, - wired: true, - ); + ConnectionProtocol get connection => DefaultConnectionProtocol(); } diff --git a/lib/src/infrastructure/yubidart_ios.dart b/lib/src/infrastructure/yubidart_ios.dart index b068cc7..4451a2b 100644 --- a/lib/src/infrastructure/yubidart_ios.dart +++ b/lib/src/infrastructure/yubidart_ios.dart @@ -1,8 +1,6 @@ -import 'package:yubidart/src/domain/protocol/general/protocol.dart'; -import 'package:yubidart/src/domain/protocol/piv/protocol.dart'; +import 'package:yubidart/src/domain/protocol/connection/protocol.dart'; import 'package:yubidart/src/domain/yubidart_platform_interface.dart'; -import 'package:yubidart/src/infrastructure/protocol/general/default_general_protocol.dart'; -import 'package:yubidart/src/infrastructure/protocol/piv/default_piv_protocol.dart'; +import 'package:yubidart/src/infrastructure/protocol/connection/default_connection_protocol.dart'; /// An implementation of [YubidartPlatform] that uses method channels. class YubidartIos extends YubidartPlatform { @@ -11,8 +9,5 @@ class YubidartIos extends YubidartPlatform { } @override - GeneralProtocol get general => DefaultGeneralProtocol(); - - @override - PivProtocol get piv => DefaultPivProtocol(); + ConnectionProtocol get connection => DefaultConnectionProtocol(); } diff --git a/lib/yubidart.dart b/lib/yubidart.dart index c9bafe6..e2cd35c 100644 --- a/lib/yubidart.dart +++ b/lib/yubidart.dart @@ -1,9 +1,6 @@ import 'package:yubidart/src/domain/protocol/protocol.dart'; import 'package:yubidart/src/domain/yubidart_platform_interface.dart'; -import 'package:yubidart/src/infrastructure/protocol/otp/default_otp_protocol.dart'; -import 'package:yubidart/src/infrastructure/protocol/otp/yubicloud_client.dart'; - export 'package:cryptography/dart.dart'; export 'src/domain/model/model.dart'; @@ -12,9 +9,5 @@ export 'src/infrastructure/yubidart_android.dart'; export 'src/infrastructure/yubidart_ios.dart'; class Yubidart { - GeneralProtocol get general => YubidartPlatform.instance.general; - - OTPProtocol get otp => DefaultOTPProtocol(yubicloudClient: YubicloudClient()); - - PivProtocol get piv => YubidartPlatform.instance.piv; + ConnectionProtocol get connection => YubidartPlatform.instance.connection; }