Skip to content

Commit

Permalink
✨ Rewrite using native YubiKit lib.
Browse files Browse the repository at this point in the history
  • Loading branch information
Chralu authored and redDwarf03 committed Feb 2, 2023
1 parent 74fae52 commit 2e591ce
Show file tree
Hide file tree
Showing 134 changed files with 4,111 additions and 477 deletions.
31 changes: 5 additions & 26 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
Expand All @@ -21,31 +22,9 @@
#.vscode/

# Flutter/Dart/Pub related
**/ios/Flutter/.last_build_id
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.vscode/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

# Web related
lib/generated_plugin_registrant.dart

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
.vscode/launch.json
ios/Flutter/Release.xcconfig
ios/Flutter/Debug.xcconfig
macos/Flutter/Flutter-Debug.xcconfig
macos/Flutter/Flutter-Release.xcconfig
build/
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

#### Version 2.0.0-dev.1 (2023-XX-XX)
* Add PIV protocol
* BREAKING CHANGES :
* `YubicoService().verifyYubiCloudOTP(otp, yubikeyClientAPIKey, yubikeyClientID);` becomes `Yubidart().otp.verify(otp, yubikeyClientAPIKey, yubikeyClientID);`
* TODO :
* separate connection/authentication/processing actions

#### Version 1.0.4 (2022-08-16)
* h param is not good when we receive a '+'
* Update project (dependencies, lints, dart version)
Expand Down
2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ linter:
- always_put_control_body_on_new_line
# - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219
- always_require_non_null_named_parameters
- always_specify_types
# - always_specify_types
- annotate_overrides
# - avoid_annotating_with_dynamic # conflicts with always_specify_types
- avoid_bool_literals_in_conditional_expressions
Expand Down
9 changes: 9 additions & 0 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx
51 changes: 51 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
group 'net.archethic.yubikit_android'
version '1.0-SNAPSHOT'

buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

allprojects {
repositories {
google()
mavenCentral()
}
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 31

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = '1.8'
}

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}

defaultConfig {
minSdkVersion 19
}

dependencies {
implementation 'com.yubico.yubikit:android:2.1.0'
implementation 'com.yubico.yubikit:piv:2.1.0'
}
}
1 change: 1 addition & 0 deletions android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
3 changes: 3 additions & 0 deletions android/settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
rootProject.name = 'yubikit_android'


3 changes: 3 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.archethic.yubikit_android">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package net.archethic.yubikit_android

import android.app.Activity
import android.content.Context
import android.nfc.NfcAdapter
import android.util.Log
import androidx.annotation.NonNull
import com.yubico.yubikit.android.YubiKitManager
import com.yubico.yubikit.android.transport.nfc.NfcConfiguration
import com.yubico.yubikit.android.transport.nfc.NfcNotAvailable
import com.yubico.yubikit.core.smartcard.ApduException
import com.yubico.yubikit.core.smartcard.SW.*
import com.yubico.yubikit.core.smartcard.SmartCardConnection
import com.yubico.yubikit.piv.*
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import java.security.KeyFactory
import java.security.interfaces.ECPublicKey
import java.security.spec.X509EncodedKeySpec
import java.util.*


/** YubikitAndroidPlugin */
class YubikitAndroidPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel: MethodChannel
private lateinit var context: Context
private lateinit var activity: Activity
private lateinit var yubikitManager: YubiKitManager

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "net.archethic/yubidart")
channel.setMethodCallHandler(this)
context = flutterPluginBinding.applicationContext
yubikitManager = YubiKitManager(context)
}

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
"isNfcEnabled" -> {
val adapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(context);

result.success(adapter != null && adapter.isEnabled());
}
"pivCalculateSecret" -> {
Log.d("PIV Calculate secret", "begin")

val arguments = call.arguments as? HashMap<String, Any>
val pin = arguments?.get("pin") as? String
val slot = when (val rawSlot = arguments?.get("slot") as? Int) {
null -> null
else -> Slot.fromValue(rawSlot)
}
val peerPublicKey =
when (val rawPeerPublicKey = arguments?.get("peerPublicKey") as? ByteArray) {
null -> null
else -> KeyFactory.getInstance("EC")
.generatePublic(X509EncodedKeySpec(rawPeerPublicKey)) as ECPublicKey
}


if (slot == null || peerPublicKey == null) {
result.error(
YubikitError.dataError.code,
"Data or format error",
call.arguments,
)
return
}

Log.d("PIV Calculate secret", "arguments parsed")
yubikitManager.startNfcDiscovery(NfcConfiguration(), activity) { device ->
device.requestConnection(SmartCardConnection::class.java) { connectionResult ->
guard(result) {
Log.d("PIV Calculate secret", "device discovered")

val connection = connectionResult.getValue()
val piv = PivSession(connection)
Log.d("PIV Calculate secret", "piv session ok")

if (pin != null) {
piv.verifyPin(
pin.toCharArray()
)
}

val secret = piv.calculateSecret(slot, peerPublicKey)
Log.d("PIV Calculate secret", "secret calculated : $secret")

result.success(secret)
}
}
}
}
"pivGenerateKey" -> {
Log.d("AUTHENT START", "GO")

val arguments = call.arguments as? HashMap<String, Any>
val pin = arguments?.get("pin") as? String
val managementKey = arguments?.get("managementKey") as? ByteArray
val managementKeyType =
when (val rawManagementKeyType = arguments?.get("managementKeyType") as? Int) {
null -> null
else -> ManagementKeyType.fromValue(rawManagementKeyType.toByte())
}
val slot = when (val rawSlot = arguments?.get("slot") as? Int) {
null -> null
else -> Slot.fromValue(rawSlot)
}
val keyType = when (val rawKeyType = arguments?.get("type") as? Int) {
null -> null
else -> KeyType.fromValue(rawKeyType)
}
val pinPolicy = when (val rawPinPolicy = arguments?.get("pinPolicy") as? Int) {
null -> null
else -> PinPolicy.fromValue(rawPinPolicy)
}
val touchPolicy =
when (val rawTouchPolicy = arguments?.get("touchPolicy") as? Int) {
null -> null
else -> TouchPolicy.fromValue(rawTouchPolicy)
}

if (pin == null || managementKey == null || managementKeyType == null || slot == null || keyType == null || pinPolicy == null || touchPolicy == null) {
result.error(
YubikitError.dataError.code,
"Data or format error",
call.arguments,
)
return
}

Log.d("AUTHENTICATE", "BEFORE")

yubikitManager.startNfcDiscovery(NfcConfiguration(), activity) { device ->
device.requestConnection(SmartCardConnection::class.java) { connectionResult ->
guard(result) {
val connection = connectionResult.getValue()
val piv = PivSession(connection)
Log.d("AUTHENTICATE", "GO")
piv.authenticate(
managementKeyType,
managementKey,
)
piv.verifyPin(
pin.toCharArray()
)
val publicKey = piv.generateKey(
slot,
keyType,
pinPolicy,
touchPolicy,
)

Log.d("AUTHENTICATE", "DONE")
result.success(publicKey.encoded)
}
}
}
}
"pivGetCertificate" -> {
Log.d("PIV Get Certificate", "Start")

val arguments = call.arguments as? HashMap<String, Any>
val pin = arguments?.get("pin") as? String
val slot = when (val rawSlot = arguments?.get("slot") as? Int) {
null -> null
else -> Slot.fromValue(rawSlot)
}


if (pin == null || slot == null) {
result.error(
YubikitError.dataError.code,
"Data or format error",
call.arguments,
)
return
}

Log.d("PIV Get Certificate", "Params parsed")

yubikitManager.startNfcDiscovery(NfcConfiguration(), activity) { device ->
device.requestConnection(SmartCardConnection::class.java) { connectionResult ->
guard(result) {
val connection = connectionResult.getValue()
val piv = PivSession(connection)
Log.d("PIV Get Certificate", "GO")
piv.verifyPin(
pin.toCharArray()
)
Log.d("PIV Get Certificate", "Authentication OK")
val certificate = piv.getCertificate(slot)
Log.d("PIV Get Certificate", "DONE")
result.success(certificate.encoded)
}
}
}
}
else -> {
result.notImplemented()
}
}
}

override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}

override fun onDetachedFromActivity() {
}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity;
}

override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity;
}

override fun onDetachedFromActivityForConfigChanges() {
}
}
Loading

0 comments on commit 2e591ce

Please sign in to comment.