diff --git a/resources/ausweisapp.qrc b/resources/ausweisapp.qrc
index a845aba..400320a 100644
--- a/resources/ausweisapp.qrc
+++ b/resources/ausweisapp.qrc
@@ -74,9 +74,11 @@
images/icon_nfc.svg
images/icon_remote.svg
images/icon_bluetooth.svg
+ images/icon_omapi.svg
images/phone_nfc.svg
images/phone_remote.svg
images/phone_bluetooth.svg
+ images/phone_omapi.svg
images/location_flag_de.svg
images/location_flag_en.svg
images/siteWithLogo.png
diff --git a/resources/images/icon_omapi.svg b/resources/images/icon_omapi.svg
new file mode 100644
index 0000000..6769f10
--- /dev/null
+++ b/resources/images/icon_omapi.svg
@@ -0,0 +1,16 @@
+
+
diff --git a/resources/images/phone_omapi.svg b/resources/images/phone_omapi.svg
new file mode 100644
index 0000000..47abc05
--- /dev/null
+++ b/resources/images/phone_omapi.svg
@@ -0,0 +1,18 @@
+
+
diff --git a/resources/packaging/android/AndroidManifest.xml.apk.in b/resources/packaging/android/AndroidManifest.xml.apk.in
index 9e19b92..cebe1a6 100644
--- a/resources/packaging/android/AndroidManifest.xml.apk.in
+++ b/resources/packaging/android/AndroidManifest.xml.apk.in
@@ -157,7 +157,17 @@
+
+
+
+
+
+
+
+
@@ -173,6 +183,9 @@
+
+
+
diff --git a/resources/packaging/android/build.gradle.append b/resources/packaging/android/build.gradle.append
index 19efe91..1892d10 100644
--- a/resources/packaging/android/build.gradle.append
+++ b/resources/packaging/android/build.gradle.append
@@ -6,10 +6,14 @@ task sourcesJar(type: Jar) {
dependencies {
compile "com.android.support:support-v4:21.0.3"
+ compile 'net.vx4:lib.omw.ivid:0.0.1-SNAPSHOT'
+ provided 'net.vx4:lib.omw.omapi:0.0.1-SNAPSHOT'
}
allprojects {
repositories {
+ mavenLocal()
+
maven {
url "https://maven.google.com"
}
diff --git a/resources/qml/Governikus/ChangePinView/ChangePinController.qml b/resources/qml/Governikus/ChangePinView/ChangePinController.qml
index 1b0cd8d..c5f13a3 100644
--- a/resources/qml/Governikus/ChangePinView/ChangePinController.qml
+++ b/resources/qml/Governikus/ChangePinView/ChangePinController.qml
@@ -82,7 +82,11 @@ Item {
}
break
case "StateConnectCard":
- setPinWorkflowStateAndContinue(ChangePinController.WorkflowStates.Card)
+ if (d.readerPlugInType === ReaderPlugIn.OMAPI) {
+ controller.workflowState = IdentifyController.WorkflowStates.Card
+ } else {
+ setIdentifyWorkflowStateAndContinue(IdentifyController.WorkflowStates.Card)
+ }
break
case "StatePreparePace":
fireReplace(pinProgressView)
diff --git a/resources/qml/Governikus/IdentifyView/+mobile/IdentifyController.qml b/resources/qml/Governikus/IdentifyView/+mobile/IdentifyController.qml
index 15c875f..f917f94 100644
--- a/resources/qml/Governikus/IdentifyView/+mobile/IdentifyController.qml
+++ b/resources/qml/Governikus/IdentifyView/+mobile/IdentifyController.qml
@@ -111,7 +111,11 @@ Item {
}
break
case "StateConnectCard":
- setIdentifyWorkflowStateAndContinue(IdentifyController.WorkflowStates.Card)
+ if (d.readerPlugInType === ReaderPlugIn.OMAPI) {
+ controller.workflowState = IdentifyController.WorkflowStates.Card
+ } else {
+ setIdentifyWorkflowStateAndContinue(IdentifyController.WorkflowStates.Card)
+ }
break
case "StatePreparePace":
fireReplace(identifyProgressView)
diff --git a/resources/qml/Governikus/TechnologyInfo/+android/TechnologySwitch.qml b/resources/qml/Governikus/TechnologyInfo/+android/TechnologySwitch.qml
index a62f492..d44caa8 100644
--- a/resources/qml/Governikus/TechnologyInfo/+android/TechnologySwitch.qml
+++ b/resources/qml/Governikus/TechnologyInfo/+android/TechnologySwitch.qml
@@ -38,5 +38,12 @@ Rectangle {
imageSource: "qrc:///images/icon_bluetooth.svg"
text: qsTr("Bluetooth") + settingsModel.translationTrigger
}
+
+ TechnologySwitchButton {
+ buttonActive: selectedTechnology !== ReaderPlugIn.OMAPI
+ onClicked: baseItem.requestPluginType(ReaderPlugIn.OMAPI)
+ imageSource: "qrc:///images/icon_omapi.svg"
+ text: qsTr("meID") + settingsModel.translationTrigger
+ }
}
}
diff --git a/resources/qml/Governikus/Workflow/+mobile/GeneralWorkflow.qml b/resources/qml/Governikus/Workflow/+mobile/GeneralWorkflow.qml
index 5998e93..668d409 100644
--- a/resources/qml/Governikus/Workflow/+mobile/GeneralWorkflow.qml
+++ b/resources/qml/Governikus/Workflow/+mobile/GeneralWorkflow.qml
@@ -51,4 +51,12 @@ SectionPage
onContinueWorkflow: workflowModel.continueWorkflow()
onRequestPluginType: workflowModel.readerPlugInType = pReaderPlugInType;
}
+
+ OmapiWorkflow
+ {
+ anchors.fill: parent
+ waitingFor: baseItem.waitingFor
+ visible: d.readerPlugInType === ReaderPlugIn.OMAPI
+ onRequestPluginType: workflowModel.readerPlugInType = pReaderPlugInType;
+ }
}
diff --git a/resources/qml/Governikus/Workflow/+mobile/OmapiWorkflow.qml b/resources/qml/Governikus/Workflow/+mobile/OmapiWorkflow.qml
new file mode 100644
index 0000000..88322fd
--- /dev/null
+++ b/resources/qml/Governikus/Workflow/+mobile/OmapiWorkflow.qml
@@ -0,0 +1,55 @@
+import QtQuick 2.10
+import QtQuick.Layouts 1.1
+
+import Governikus.Global 1.0
+import Governikus.TechnologyInfo 1.0
+import Governikus.Type.ApplicationModel 1.0
+import Governikus.Type.ReaderPlugIn 1.0
+import Governikus.Type.NumberModel 1.0
+
+
+Item {
+ id: baseItem
+ signal requestPluginType(int pReaderPlugInType)
+ property int waitingFor: 0
+ clip: true
+
+ ProgressIndicator {
+ id: progressIndicator
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.right: parent.right
+ height: parent.height / 2
+ imageIconSource: "qrc:///images/icon_omapi.svg"
+ imagePhoneSource: "qrc:///images/phone_omapi.svg"
+ state: baseItem.waitingFor === Workflow.WaitingFor.Reader ? "off" : "one"
+ }
+
+ TechnologyInfo {
+ id: technologyInfo
+
+ anchors.left: parent.left
+ anchors.leftMargin: Utils.dp(5)
+ anchors.right: parent.right
+ anchors.rightMargin: anchors.leftMargin
+ anchors.top: progressIndicator.bottom
+ anchors.bottom: technologySwitch.top
+ clip: true
+
+ enableButtonVisible: baseItem.waitingFor !== Workflow.WaitingFor.Reader
+ enableButtonText: qsTr("Continue") + settingsModel.translationTrigger
+ enableText: (enableButtonVisible ? qsTr("Please confirm the usage of your mobile eID.") : "") + settingsModel.translationTrigger
+ onEnableClicked: workflowModel.continueWorkflow()
+ titleText: qsTr("No mobile eID found") + settingsModel.translationTrigger
+ subTitleText: qsTr("Please ensure that a SIM card is inserted and a suitable mobile eID is stored there.") + settingsModel.translationTrigger
+ }
+
+ TechnologySwitch {
+ id: technologySwitch
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ selectedTechnology: ReaderPlugIn.OMAPI
+ onRequestPluginType: parent.requestPluginType(pReaderPlugInType)
+ }
+}
diff --git a/resources/qml/Governikus/Workflow/qmldir b/resources/qml/Governikus/Workflow/qmldir
index 07e5325..157ea94 100644
--- a/resources/qml/Governikus/Workflow/qmldir
+++ b/resources/qml/Governikus/Workflow/qmldir
@@ -5,6 +5,7 @@ internal BusyImageIndicator BusyImageIndicator.qml
internal CardReader CardReader.qml
internal NfcProgressIndicator NfcProgressIndicator.qml
internal NfcWorkflow NfcWorkflow.qml
+internal OmapiWorkflow OmapiWorkflow.qml
internal ProgressCircle ProgressCircle.qml
internal ProgressIndicator ProgressIndicator.qml
internal RemoteWorkflow RemoteWorkflow.qml
diff --git a/resources/translations/ausweisapp2_de.ts b/resources/translations/ausweisapp2_de.ts
index 0b9574d..6b7d903 100644
--- a/resources/translations/ausweisapp2_de.ts
+++ b/resources/translations/ausweisapp2_de.ts
@@ -1306,6 +1306,25 @@
Bitte platzieren Sie Ihr Gerät<br/>über Ihrem Personalausweis.
+
+ OmapiWorkflow
+
+
+ Fortsetzen
+
+
+
+ Bitte bestätigen Sie die Verwendung ihrer mobilen eID.
+
+
+
+ Keine mobile eID gefunden
+
+
+
+ Bitte stellen Sie sicher, dass eine SIM-Karte eingelegt ist und eine passende mobile eID hinterlegt ist.
+
+
PinSettingsWidget
@@ -2126,6 +2145,10 @@ Bitte beachten Sie, dass Sie mit Ihrer PUK lediglich Ihren Online-Ausweis entspe
Bluetooth Kartenlesegerät anstelle <br/>vom WLAN Kartenlesegerät verwenden
+
+
+ meID
+
TitleBar
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index fcb7696..04333c5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -206,6 +206,7 @@ IF(ANDROID)
TARGET_LINK_LIBRARIES(AusweisApp AusweisAppCardNfc)
IF(NOT ANDROID_BUILD_AAR)
TARGET_LINK_LIBRARIES(AusweisApp AusweisAppActivationIntent)
+ TARGET_LINK_LIBRARIES(AusweisApp AusweisAppCardOmapi)
ENDIF()
ENDIF()
diff --git a/src/card/CMakeLists.txt b/src/card/CMakeLists.txt
index 1ba6fb9..f4f5983 100644
--- a/src/card/CMakeLists.txt
+++ b/src/card/CMakeLists.txt
@@ -23,4 +23,8 @@ IF(TARGET Qt5::Bluetooth)
ADD_SUBDIRECTORY(bluetooth)
ENDIF()
+IF(ANDROID)
+ ADD_SUBDIRECTORY(omapi)
+ENDIF()
+
ADD_SUBDIRECTORY(drivers)
diff --git a/src/card/base/ReaderManagerPlugInInfo.h b/src/card/base/ReaderManagerPlugInInfo.h
index 199c3d7..cd02d15 100644
--- a/src/card/base/ReaderManagerPlugInInfo.h
+++ b/src/card/base/ReaderManagerPlugInInfo.h
@@ -15,7 +15,7 @@
namespace governikus
{
-defineEnumType(ReaderManagerPlugInType, UNKNOWN, PCSC, BLUETOOTH, NFC, REMOTE)
+defineEnumType(ReaderManagerPlugInType, UNKNOWN, PCSC, BLUETOOTH, NFC, REMOTE, OMAPI)
class ReaderManagerPlugInInfo
diff --git a/src/card/omapi/CMakeLists.txt b/src/card/omapi/CMakeLists.txt
new file mode 100644
index 0000000..b1979e7
--- /dev/null
+++ b/src/card/omapi/CMakeLists.txt
@@ -0,0 +1,15 @@
+#####################################################################
+# The ReaderManagerPlugin for OMAPI.
+#
+# This plugin is a wrapper for the Virtual eID on a SIM card.
+# Supported: Android.
+#####################################################################
+
+ADD_PLATFORM_LIBRARY(AusweisAppCardOmapi)
+
+TARGET_LINK_LIBRARIES(AusweisAppCardOmapi Qt5::Core AusweisAppGlobal AusweisAppCard)
+TARGET_COMPILE_DEFINITIONS(AusweisAppCardOmapi PRIVATE QT_STATICPLUGIN)
+
+IF(ANDROID)
+ TARGET_LINK_LIBRARIES(AusweisAppCardOmapi Qt5::AndroidExtras)
+ENDIF()
diff --git a/src/card/omapi/Omapi.java b/src/card/omapi/Omapi.java
new file mode 100644
index 0000000..fa577a0
--- /dev/null
+++ b/src/card/omapi/Omapi.java
@@ -0,0 +1,51 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+package com.governikus.ausweisapp2.omapi;
+
+import java.io.Closeable;
+import java.util.List;
+
+import com.governikus.ausweisapp2.omapi.OmapiError;
+import com.governikus.ausweisapp2.omapi.OmapiReader;
+
+/**
+ *
+ */
+public interface Omapi extends Closeable // AutoClosable requires min. API level 19
+{
+ /**
+ * Initialize omapi layer.
+ * This must be called if isAvailable() = false
+ *
+ * @return Null on success, otherwise an error.
+ */
+ OmapiError init();
+
+ /**
+ * Check if omapi layer is available.
+ *
+ * @return True if omapi layer exists, otherwise false.
+ */
+ boolean isAvailable();
+
+ /**
+ * Check if omapi layer is enabled.
+ *
+ * @return True if omapi layer is enabled, otherwise false.
+ */
+ boolean isEnabled();
+
+ /**
+ * Get all enabled reader.
+ *
+ * @return List of reader, otherwise empty list.
+ */
+ List getReader();
+
+ /**
+ * Closes and destroys the currently allocated implementation instance.
+ */
+ void close();
+}
diff --git a/src/card/omapi/OmapiCard.cpp b/src/card/omapi/OmapiCard.cpp
new file mode 100644
index 0000000..a6aa1bc
--- /dev/null
+++ b/src/card/omapi/OmapiCard.cpp
@@ -0,0 +1,194 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+#include "OmapiCard.h"
+
+#include "DestroyPaceChannel.h"
+#include "EstablishPaceChannel.h"
+
+#include
+
+using namespace governikus;
+
+
+Q_DECLARE_LOGGING_CATEGORY(card_omapi)
+
+
+OmapiCard::OmapiCard()
+ : Card()
+ , mIsValid(true)
+ , mConnected(false)
+{
+}
+
+
+bool OmapiCard::isValid() const
+{
+ return mIsValid;
+}
+
+
+CardReturnCode OmapiCard::connect()
+{
+ mConnected = true;
+ return CardReturnCode::OK;
+}
+
+
+CardReturnCode OmapiCard::disconnect()
+{
+ mConnected = false;
+ return CardReturnCode::OK;
+}
+
+
+bool OmapiCard::isConnected()
+{
+ return mConnected;
+}
+
+
+#ifdef Q_OS_ANDROID
+QByteArray OmapiCard::sendData(const QByteArray& pData)
+{
+ QByteArray ret;
+ QAndroidJniEnvironment env;
+ jbyteArray cmd = convert(pData);
+
+ const QAndroidJniObject javaActivity(QtAndroid::androidActivity());
+ if (!javaActivity.isValid())
+ {
+ qCritical() << "Cannot determine android activity";
+ return ret;
+ }
+
+ const auto response = QAndroidJniObject::callStaticObjectMethod("com/governikus/ausweisapp2/omapi/impl/OmapiJNI",
+ "transmit",
+ "([B)[B",
+ cmd);
+
+ if (!response.isValid())
+ {
+ return ret;
+ }
+
+ ret = convert(static_cast(response.object()));
+ if (env->ExceptionCheck())
+ {
+ qCritical() << "Cannot call Omapi.transmit()";
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+ env->DeleteLocalRef(cmd);
+
+ return ret;
+}
+
+
+QByteArray OmapiCard::convert(const jbyteArray& pData)
+{
+ QAndroidJniEnvironment env;
+
+ jsize size = env->GetArrayLength(pData);
+ QVector buffer(size);
+ env->GetByteArrayRegion(pData, 0, size, buffer.data());
+
+ return QByteArray(reinterpret_cast(buffer.data()), buffer.size());
+}
+
+
+jbyteArray OmapiCard::convert(const QByteArray& pData)
+{
+ QAndroidJniEnvironment env;
+ const int size = pData.size();
+ const char* buffer = pData.constData();
+
+ jbyteArray target = env->NewByteArray(size);
+ jbyte* bytes = env->GetByteArrayElements(target, 0);
+ for (int i = 0; i < size; ++i)
+ {
+ bytes[i] = static_cast(buffer[i]);
+ }
+ env->SetByteArrayRegion(target, 0, size, bytes);
+
+ return target;
+}
+
+
+CardReturnCode OmapiCard::establishPaceChannel(PacePasswordId pPasswordId,
+ const QByteArray& pChat,
+ const QByteArray& pCertificateDescription,
+ EstablishPaceChannelOutput& pChannelOutput, quint8 pTimeoutSeconds)
+{
+ Q_UNUSED(pTimeoutSeconds);
+
+ EstablishPaceChannel builder;
+ builder.setPasswordId(pPasswordId);
+ builder.setChat(pChat);
+ builder.setCertificateDescription(pCertificateDescription);
+
+ QAndroidJniEnvironment env;
+ jbyteArray paceData = convert(builder.createCommandData());
+
+ const QAndroidJniObject javaActivity(QtAndroid::androidActivity());
+ if (!javaActivity.isValid())
+ {
+ qCritical() << "Cannot determine android activity";
+ return CardReturnCode::COMMAND_FAILED;
+ }
+
+ const auto response = QAndroidJniObject::callStaticObjectMethod("com/governikus/ausweisapp2/omapi/impl/OmapiJNI",
+ "paceControl",
+ "([B)[B",
+ paceData);
+
+ if (!response.isValid())
+ {
+ return CardReturnCode::COMMAND_FAILED;
+ }
+
+ if (env->ExceptionCheck())
+ {
+ qCritical() << "Cannot call Omapi.paceControl()";
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+
+ env->DeleteLocalRef(paceData);
+ paceData = static_cast(response.object());
+ if (env->IsSameObject(paceData, NULL))
+ {
+ qCritical() << "Control to establish PACE channel failed";
+ return CardReturnCode::COMMAND_FAILED;
+ }
+
+ pChannelOutput.parse(convert(paceData), pPasswordId);
+ return pChannelOutput.getPaceReturnCode();
+}
+
+
+CardReturnCode OmapiCard::destroyPaceChannel()
+{
+ // TODO: to be completed, for now PACE channel is destroyed implicitly on establish()
+ return CardReturnCode::OK;
+}
+
+
+#endif
+
+CardReturnCode OmapiCard::transmit(const CommandApdu& pCmd, ResponseApdu& pRes)
+{
+#ifdef Q_OS_ANDROID
+ const auto& data = pCmd.getBuffer();
+ qCDebug(card_omapi) << "transmit to omapi:" << data;
+ const auto& response = sendData(data);
+ qCDebug(card_omapi) << "received from omapi:" << response;
+ pRes.setBuffer(response);
+
+#else
+ Q_UNUSED(pCmd)
+ Q_UNUSED(pRes)
+#endif
+ return CardReturnCode::OK;
+}
diff --git a/src/card/omapi/OmapiCard.h b/src/card/omapi/OmapiCard.h
new file mode 100644
index 0000000..1ff3a32
--- /dev/null
+++ b/src/card/omapi/OmapiCard.h
@@ -0,0 +1,51 @@
+/*!
+ * \brief Implementation of \ref Card for Omapi.
+ *
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+#pragma once
+
+#include "Card.h"
+
+#ifdef Q_OS_ANDROID
+#include
+#include
+#include
+#endif
+
+namespace governikus
+{
+class OmapiCard
+ : public Card
+{
+ Q_OBJECT
+
+ private:
+ bool mIsValid;
+ bool mConnected;
+
+#ifdef Q_OS_ANDROID
+ QByteArray convert(const jbyteArray& pData);
+ jbyteArray convert(const QByteArray& pData);
+ QByteArray sendData(const QByteArray& pData);
+#endif
+
+ public:
+ OmapiCard();
+ virtual ~OmapiCard() override = default;
+
+ bool isValid() const;
+
+ virtual CardReturnCode connect() override;
+ virtual CardReturnCode disconnect() override;
+ virtual bool isConnected() override;
+
+ virtual CardReturnCode transmit(const CommandApdu& pCmd, ResponseApdu& pRes) override;
+
+ virtual CardReturnCode establishPaceChannel(PacePasswordId pPasswordId, const QByteArray& pChat, const QByteArray& pCertificateDescription, EstablishPaceChannelOutput& pChannelOutput, quint8 pTimeoutSeconds) override;
+
+ virtual CardReturnCode destroyPaceChannel() override;
+};
+
+} // namespace governikus
diff --git a/src/card/omapi/OmapiCard.java b/src/card/omapi/OmapiCard.java
new file mode 100644
index 0000000..c0b6b27
--- /dev/null
+++ b/src/card/omapi/OmapiCard.java
@@ -0,0 +1,18 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+package com.governikus.ausweisapp2.omapi;
+
+/**
+ *
+ */
+public interface OmapiCard
+{
+ /**
+ * Send an APDU to the card.
+ *
+ * @return Response apdu, null on error.
+ */
+ byte[] transmit(final byte[] apdu);
+}
diff --git a/src/card/omapi/OmapiError.java b/src/card/omapi/OmapiError.java
new file mode 100644
index 0000000..26373c8
--- /dev/null
+++ b/src/card/omapi/OmapiError.java
@@ -0,0 +1,49 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+package com.governikus.ausweisapp2.omapi;
+
+/**
+ *
+ */
+public class OmapiError
+{
+ private final String error, desc;
+
+ /**
+ * Create error instance.
+ *
+ * @param error
+ * @param desc
+ */
+ public OmapiError(final String error, final String desc)
+ {
+ this.error = error;
+ this.desc = desc;
+ }
+
+
+ /**
+ * Get name of error.
+ *
+ * @return
+ */
+ public String getName()
+ {
+ return error;
+ }
+
+
+ /**
+ * Get a human readable description of the error.
+ *
+ * @return
+ */
+ public String getDescription()
+ {
+ return desc;
+ }
+
+
+}
diff --git a/src/card/omapi/OmapiReader.cpp b/src/card/omapi/OmapiReader.cpp
new file mode 100644
index 0000000..a9bad50
--- /dev/null
+++ b/src/card/omapi/OmapiReader.cpp
@@ -0,0 +1,55 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+#include "CardConnectionWorker.h"
+#include "OmapiReader.h"
+
+#include
+
+
+using namespace governikus;
+
+
+Q_DECLARE_LOGGING_CATEGORY(card_omapi)
+
+
+OmapiReader::OmapiReader()
+ : ConnectableReader(ReaderManagerPlugInType::OMAPI, QStringLiteral("omapi"))
+ , mConnected(false)
+{
+ mReaderInfo.setBasicReader(false);
+ mReaderInfo.setConnected(true);
+ QMetaObject::invokeMethod(this, &Reader::update, Qt::QueuedConnection);
+}
+
+
+void OmapiReader::connectReader()
+{
+ mConnected = true;
+
+ qCDebug(card_omapi) << "create card";
+ mOmapiCard.reset(new OmapiCard);
+ QSharedPointer cardConnection = createCardConnectionWorker();
+ CardInfoFactory::create(cardConnection, mReaderInfo);
+ Q_EMIT fireCardInserted(getName());
+}
+
+
+void OmapiReader::disconnectReader()
+{
+ mConnected = false;
+ mOmapiCard.reset(new OmapiCard);
+}
+
+
+Card* OmapiReader::getCard() const
+{
+ return mOmapiCard.data();
+}
+
+
+Reader::CardEvent OmapiReader::updateCard()
+{
+ return Reader::CardEvent::NONE;
+}
diff --git a/src/card/omapi/OmapiReader.h b/src/card/omapi/OmapiReader.h
new file mode 100644
index 0000000..9ebe001
--- /dev/null
+++ b/src/card/omapi/OmapiReader.h
@@ -0,0 +1,37 @@
+/*!
+ * \brief Implementation of \ref Reader for Omapi.
+ *
+ * \copyright Copyright (c) 2015-2019 Governikus GmbH & Co. KG, Germany
+ */
+
+#pragma once
+
+#include "OmapiCard.h"
+#include "Reader.h"
+
+#include
+
+namespace governikus
+{
+
+class OmapiReader
+ : public ConnectableReader
+{
+ Q_OBJECT
+
+ private:
+ bool mConnected;
+ QScopedPointer mOmapiCard;
+
+ public:
+ OmapiReader();
+ virtual ~OmapiReader() override = default;
+
+ virtual void connectReader() override;
+ virtual void disconnectReader() override;
+
+ virtual Card* getCard() const override;
+ virtual CardEvent updateCard() override;
+};
+
+} // namespace governikus
diff --git a/src/card/omapi/OmapiReader.java b/src/card/omapi/OmapiReader.java
new file mode 100644
index 0000000..5eab254
--- /dev/null
+++ b/src/card/omapi/OmapiReader.java
@@ -0,0 +1,21 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+package com.governikus.ausweisapp2.omapi;
+
+/**
+ *
+ */
+public interface OmapiReader
+{
+ /**
+ * Get name of reader.
+ */
+ String getName();
+
+ /**
+ * Get inserted card, null if not inserted.
+ */
+ OmapiCard getCard();
+}
diff --git a/src/card/omapi/OmapiReaderManagerPlugIn.cpp b/src/card/omapi/OmapiReaderManagerPlugIn.cpp
new file mode 100644
index 0000000..6764a6e
--- /dev/null
+++ b/src/card/omapi/OmapiReaderManagerPlugIn.cpp
@@ -0,0 +1,108 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+#include "OmapiReaderManagerPlugIn.h"
+
+#include
+
+#ifdef Q_OS_ANDROID
+#include
+#include
+#include
+#endif
+
+using namespace governikus;
+
+
+Q_DECLARE_LOGGING_CATEGORY(card_omapi)
+
+OmapiReaderManagerPlugIn::OmapiReaderManagerPlugIn()
+ : ReaderManagerPlugIn(ReaderManagerPlugInType::OMAPI, true, true)
+ , mEnabled(false)
+ , mReader(new OmapiReader)
+{
+ connect(mReader.data(), &OmapiReader::fireCardInserted, this, &OmapiReaderManagerPlugIn::fireCardInserted);
+ connect(mReader.data(), &OmapiReader::fireCardRemoved, this, &OmapiReaderManagerPlugIn::fireCardRemoved);
+ connect(mReader.data(), &OmapiReader::fireCardRetryCounterChanged, this, &OmapiReaderManagerPlugIn::fireCardRetryCounterChanged);
+ connect(mReader.data(), &OmapiReader::fireReaderPropertiesUpdated, this, &OmapiReaderManagerPlugIn::fireReaderPropertiesUpdated);
+ qCDebug(card_omapi) << "Add reader" << mReader->getName();
+}
+
+
+OmapiReaderManagerPlugIn::~OmapiReaderManagerPlugIn()
+{
+}
+
+
+QList OmapiReaderManagerPlugIn::getReaders() const
+{
+ qCDebug(card_omapi) << "get my readers";
+ if (mEnabled)
+ {
+ return QList({mReader.data()});
+ }
+
+ return QList();
+}
+
+
+void OmapiReaderManagerPlugIn::startScan(bool)
+{
+ qCDebug(card_omapi) << "scan me";
+ mEnabled = true;
+ mReader->connectReader();
+ Q_EMIT fireReaderAdded(mReader->getName());
+}
+
+
+void OmapiReaderManagerPlugIn::stopScan()
+{
+ mEnabled = false;
+ mReader->disconnectReader();
+ Q_EMIT fireReaderRemoved(mReader->getName());
+}
+
+
+void OmapiReaderManagerPlugIn::init()
+{
+ ReaderManagerPlugIn::init();
+#ifdef Q_OS_ANDROID
+ qCDebug(card_omapi) << "omapi init";
+
+ QAndroidJniEnvironment env;
+ const QAndroidJniObject context(QtAndroid::androidContext());
+ if (!context.isValid())
+ {
+ qCritical() << "Cannot determine android context.";
+ return;
+ }
+
+ QAndroidJniObject::callStaticMethod("com/governikus/ausweisapp2/omapi/impl/OmapiJNI",
+ "init",
+ "(Landroid/content/Context;)Z",
+ context.object());
+
+ if (env->ExceptionCheck())
+ {
+ qCritical() << "Cannot call OmapiJNI.init()";
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+#endif
+}
+
+
+void OmapiReaderManagerPlugIn::shutdown()
+{
+#ifdef Q_OS_ANDROID
+ QAndroidJniEnvironment env;
+ QAndroidJniObject::callStaticMethod("com/governikus/ausweisapp2/omapi/impl/OmapiJNI", "close");
+ if (env->ExceptionCheck())
+ {
+ qCritical() << "Cannot call OmapiJNI.close()";
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ }
+#endif
+}
diff --git a/src/card/omapi/OmapiReaderManagerPlugIn.h b/src/card/omapi/OmapiReaderManagerPlugIn.h
new file mode 100644
index 0000000..1edf3ea
--- /dev/null
+++ b/src/card/omapi/OmapiReaderManagerPlugIn.h
@@ -0,0 +1,41 @@
+/*!
+ * \brief Implementation of \ref ReaderManagerPlugIn for Omapi on Android.
+ *
+ * \copyright Copyright (c) 2015-2019 Governikus GmbH & Co. KG, Germany
+ */
+
+#pragma once
+
+#include "OmapiReader.h"
+#include "ReaderManagerPlugIn.h"
+
+#include
+
+namespace governikus
+{
+
+class OmapiReaderManagerPlugIn
+ : public ReaderManagerPlugIn
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "governikus.ReaderManagerPlugIn" FILE "metadata.json")
+ Q_INTERFACES(governikus::ReaderManagerPlugIn)
+
+ private:
+ bool mEnabled;
+ QScopedPointer mReader;
+
+ public:
+ OmapiReaderManagerPlugIn();
+ virtual ~OmapiReaderManagerPlugIn() override;
+
+ virtual QList getReaders() const override;
+
+ virtual void startScan(bool pAutoConnect) override;
+ virtual void stopScan() override;
+
+ virtual void init() override;
+ virtual void shutdown() override;
+};
+
+} // namespace governikus
diff --git a/src/card/omapi/impl/OmapiImpl.java b/src/card/omapi/impl/OmapiImpl.java
new file mode 100644
index 0000000..15c38a2
--- /dev/null
+++ b/src/card/omapi/impl/OmapiImpl.java
@@ -0,0 +1,202 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+package com.governikus.ausweisapp2.omapi.impl;
+
+import android.content.Context;
+import com.governikus.ausweisapp2.omapi.Omapi;
+import com.governikus.ausweisapp2.omapi.OmapiError;
+import com.governikus.ausweisapp2.omapi.OmapiReader;
+import net.vx4.lib.omapi.Hex;
+import net.vx4.lib.omapi.OMAPITP;
+import org.simalliance.openmobileapi.Channel;
+import org.simalliance.openmobileapi.Reader;
+import org.simalliance.openmobileapi.Session;
+import org.simalliance.openmobileapi.SEService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class is the main entry point into the wrapper and handels basic logic as selecting the applet instance on the
+ * secure element.
+ *
+ * @author kahlo, 2018
+ */
+public class OmapiImpl implements Omapi, SEService.CallBack
+{
+ private final Context mContext;
+ private final SEService seService;
+ private final List readerList = new ArrayList<>();
+
+ OmapiImpl(final Context context)
+ {
+ System.out.println("OmapiImpl.OmapiImpl");
+ System.out.println("context = [" + context + "]");
+
+ this.mContext = context;
+ this.seService = new SEService(this.mContext, OmapiImpl.this);
+ System.out.println("SEService0: " + OmapiImpl.this.seService + " connected? " + OmapiImpl.this.seService.isConnected());
+ }
+
+
+ private void log(final String msg)
+ {
+ System.out.println("OmapiImpl.log: " + msg);
+ }
+
+
+ private String toHex(final byte[] in)
+ {
+ return Hex.toString(in);
+ }
+
+
+ @Override
+ public void serviceConnected(final SEService service)
+ {
+ System.out.println("OmapiImpl.serviceConnected");
+ System.out.println("service = [" + service + "]");
+
+ if (this.seService == service)
+ {
+ System.out.println("SEService1: " + this.seService + " connected? " + (this.seService == null ? "null" : this.seService.isConnected()));
+ System.out.println("SEService2: " + service + " connected? " + (service == null ? "null" : service.isConnected()));
+ }
+ else
+ {
+ System.out.println("SHOULD NOT HAPPEN: ignoring callback from foreign service = [" + service + "]");
+ }
+ }
+
+
+ @Override
+ public boolean isAvailable()
+ {
+ System.out.println("OmapiImpl.isAvailable");
+
+ for (int i = 0; i < 20 && this.seService != null && !this.seService.isConnected(); i++)
+ {
+ System.out.println("waiting for connect");
+ try
+ {
+ Thread.sleep(100);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ System.out.println("SEService*: " + this.seService + " connected? " + (this.seService == null ? "null" : this.seService.isConnected()));
+ return this.seService != null && this.seService.isConnected();
+ }
+
+
+ @Override
+ public OmapiError init()
+ {
+ if (!isAvailable())
+ {
+ return new OmapiError("No Secure Element API", "OpenMobile API not available.");
+ }
+
+ System.out.println("OmapiImpl.init");
+ final Reader[] readers = this.seService.getReaders();
+ log("readers: " + (readers != null ? readers.length : "NONE"));
+ log("readers: " + (readers != null ? Arrays.asList(readers) : "NONE"));
+
+ if (readers != null && readers.length > 0)
+ {
+ for (final Reader reader : readers)
+ {
+ try
+ {
+ log("Reader: " + reader.getName() + " - SE present? " + reader.isSecureElementPresent());
+ if (reader.isSecureElementPresent())
+ {
+ final Session session = readers[0].openSession();
+
+ log("ATR: " + toHex(session.getATR()));
+ log("Create logical channel to D2760000930101 within the session...");
+
+ final Channel channel = session.openLogicalChannel(Hex.fromString("D2760000930101"));
+ if (channel != null)
+ {
+ log("Channel: " + channel + " / " + Hex.x(channel.getSelectResponse()));
+ // TODO: check channel and status
+ // 6F0E8407D276000093010185030300FF9000
+
+ log("Send CIPHER ENABLE command");
+ final byte[] respApdu = channel.transmit(Hex.fromString("80CE000000"));
+ log("Response: " + Hex.toString(respApdu));
+ // TODO: check type depending on supported algs and name of instance if multi-SE
+ // 7C0C8004FFFFFFFF8104544553549000
+
+ final OMAPITP tp = new OMAPITP(channel);
+ new OmapiSecretFetcherImpl.Builder(this.mContext).set(tp);
+
+ readerList.add(new OmapiReaderImpl(tp));
+ }
+ }
+ }
+ catch (final Exception e)
+ {
+ // don't fail if single reader doesn't work, print log
+ //return new OmapiErrorImpl("Exception while connecting to readers", e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ }
+ else
+ {
+ return new OmapiError("No readers", "No readers available");
+ }
+
+ // no error
+ return null;
+ }
+
+
+ @Override
+ public boolean isEnabled()
+ {
+ System.out.println("OmapiImpl.isEnabled");
+ return readerList.size() > 0;
+ }
+
+
+ @Override
+ public List getReader()
+ {
+ System.out.println("OmapiImpl.getReader");
+ System.out.println("readerList = [" + readerList + "]");
+
+ return readerList;
+ }
+
+
+ @Override
+ public void close()
+ {
+ for (final OmapiReader r : this.readerList)
+ {
+ if (r != null)
+ {
+ final Channel c = ((OmapiReaderImpl) r).getChannel();
+ if (!c.isClosed())
+ {
+ c.getSession().closeChannels();
+ c.getSession().close();
+ }
+ }
+ }
+
+ this.readerList.clear();
+ // seService.shutdown();
+ }
+
+
+}
diff --git a/src/card/omapi/impl/OmapiJNI.java b/src/card/omapi/impl/OmapiJNI.java
new file mode 100644
index 0000000..aed3333
--- /dev/null
+++ b/src/card/omapi/impl/OmapiJNI.java
@@ -0,0 +1,224 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+package com.governikus.ausweisapp2.omapi.impl;
+
+import android.content.Context;
+import android.util.Log;
+import com.governikus.ausweisapp2.omapi.Omapi;
+import com.governikus.ausweisapp2.omapi.OmapiCard;
+import com.governikus.ausweisapp2.omapi.OmapiReader;
+import net.vx4.lib.omapi.ArrayTool;
+import net.vx4.lib.omapi.Hex;
+
+/**
+ * Factory for OMAPI Wrapper and simple JNI entry for the Qt code.
+ */
+public final class OmapiJNI
+{
+ private static final String LOG_TAG = "AusweisApp2.OMAPI";
+ private static final OmapiJNI INSTANCE = new OmapiJNI();
+
+ private Omapi mOmapi;
+ private OmapiCard mCard;
+
+ private OmapiJNI()
+ {
+ }
+
+
+ private static final OmapiJNI get()
+ {
+ return INSTANCE;
+ }
+
+
+ /**
+ * Initialize this factory for later usage.
+ *
+ * @param context Android {@link Context} of application (service or activity)
+ * @return
+ */
+ public static boolean init(final Context context)
+ {
+ return get()._init0(context);
+ }
+
+
+ /**
+ * Transmit data to first available and functional SE.
+ *
+ * @param apdu
+ * @return
+ */
+ public static byte[] transmit(final byte[] apdu)
+ {
+ return get()._transmit0(apdu);
+ }
+
+
+ /**
+ * Execute CCID PACE with control message
+ *
+ * @param paceData
+ * @return
+ */
+ public static byte[] paceControl(final byte[] paceData)
+ {
+ return get()._paceControl0(paceData);
+ }
+
+
+ /**
+ * Closes and destroys the currently allocated implementation instance.
+ */
+ public static void close()
+ {
+ get()._close0();
+ }
+
+
+ /**
+ * Retrieve internal service provider object.
+ *
+ * @return instance of {@link Omapi}
+ */
+ public static final Omapi getInstance()
+ {
+ return INSTANCE.mOmapi;
+ }
+
+
+ /*
+ // instance implementation
+ */
+
+ /**
+ * @param context
+ * @return
+ */
+ private final boolean _init0(final Context context)
+ {
+ Log.d(LOG_TAG, "init omapi jni");
+ System.out.println("OmapiJNI.init");
+ System.out.println("context = [" + context + "]");
+
+ if (this.mOmapi == null)
+ {
+ this.mOmapi = new OmapiImpl(context);
+ if (this.mOmapi.isAvailable())
+ {
+ this.mOmapi.init();
+ }
+ }
+
+ for (final OmapiReader reader : this.mOmapi.getReader())
+ {
+ if ((this.mCard = reader.getCard()) != null)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * @param apdu
+ * @return
+ */
+ private final byte[] _transmit0(final byte[] apdu)
+ {
+ Log.d(LOG_TAG, "transmit apdu with omapi jni");
+ System.out.println("OmapiJNI.transmit");
+ System.out.println("apdu = [" + Hex.x(apdu) + "]");
+
+ if (this.mOmapi != null)
+ {
+ if (this.mCard == null)
+ {
+ System.out.println("OmapiJNI.transmit: check for readers.");
+ for (final OmapiReader reader : this.mOmapi.getReader())
+ {
+ if ((this.mCard = reader.getCard()) != null)
+ {
+ break;
+ }
+ }
+ }
+
+ if (this.mCard != null)
+ {
+ return this.mCard.transmit(apdu);
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * @param paceData
+ * @return
+ */
+ private final byte[] _paceControl0(final byte[] paceData)
+ {
+ Log.d(LOG_TAG, "paceControl with omapi jni");
+ System.out.println("OmapiJNI.paceControl");
+ System.out.println("paceData = [" + paceData != null ? Hex.x(paceData) : "null" + "]");
+
+ if (this.mOmapi != null)
+ {
+ if (this.mCard == null)
+ {
+ System.out.println("OmapiJNI.paceControl: check for readers.");
+ for (final OmapiReader reader : this.mOmapi.getReader())
+ {
+ if ((this.mCard = reader.getCard()) != null)
+ {
+ break;
+ }
+ }
+ }
+
+ if (this.mCard != null)
+ {
+ if (paceData == null) // use as short hand for get caps or destroy?
+ { // TODO: to be completed
+ }
+
+ // keep in mind: extended length format and little to big endian conversion
+ final byte[] cmd = new byte[] {
+ (byte) 0xFF, (byte) 0x9A, (byte) 0x04, paceData[0], (byte) 0x00, paceData[2], paceData[1]
+ };
+ final byte[] res = this.mCard.transmit(ArrayTool.concat(cmd, ArrayTool.sub(paceData, 3, paceData.length - 3)));
+
+ return ArrayTool.sub(res, 0, res.length - 2);
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ *
+ */
+ private final void _close0()
+ {
+ Log.d(LOG_TAG, "transmit apdu with omapi jni");
+ try
+ {
+ this.mOmapi.close();
+ }
+ finally
+ {
+ this.mOmapi = null;
+ this.mCard = null;
+ }
+ }
+
+
+}
diff --git a/src/card/omapi/impl/OmapiReaderImpl.java b/src/card/omapi/impl/OmapiReaderImpl.java
new file mode 100644
index 0000000..b27cddd
--- /dev/null
+++ b/src/card/omapi/impl/OmapiReaderImpl.java
@@ -0,0 +1,79 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ */
+
+package com.governikus.ausweisapp2.omapi.impl;
+
+import android.util.Log;
+import com.governikus.ausweisapp2.omapi.OmapiCard;
+import com.governikus.ausweisapp2.omapi.OmapiReader;
+import net.vx4.lib.omapi.Hex;
+import net.vx4.lib.omapi.OMAPITP;
+import net.vx4.lib.omapi.TransportProvider;
+import org.simalliance.openmobileapi.Channel;
+
+/**
+ * This implementation basically wraps away specifics of the underlying transport providers.
+ *
+ * @author kahlo
+ */
+public class OmapiReaderImpl implements OmapiReader
+{
+ private static final String LOG_TAG = "AusweisApp2.OMAPI";
+
+ private final OMAPITP tp;
+
+ public OmapiReaderImpl(final OMAPITP tp)
+ {
+ this.tp = tp;
+ System.out.println("OmapiReaderImpl.OmapiReaderImpl");
+ System.out.println("tp = [" + tp + "]");
+ }
+
+
+ Channel getChannel()
+ {
+ return (Channel) ((TransportProvider) tp.getParent()).getParent();
+ }
+
+
+ @Override
+ public String getName()
+ {
+ System.out.println("OmapiReaderImpl.getName");
+ return getChannel().getSession().getReader().getName();
+ }
+
+
+ @Override
+ public OmapiCard getCard()
+ {
+ System.out.println("OmapiReaderImpl.getCard");
+
+ return new OmapiCard() {
+ @Override
+ public byte[] transmit(final byte[] apdu)
+ {
+ Log.d(LOG_TAG, "transmit omapi");
+ System.out.println("OmapiCardImpl.transmit");
+ System.out.println("apdu = [" + Hex.toString(apdu) + "]");
+
+ if (tp != null)
+ {
+ byte[] rpdu = tp.process(apdu);
+ System.out.println("rpdu = [" + Hex.toString(rpdu) + "]");
+ return rpdu;
+ }
+ else
+ {
+ System.out.println("No TransportProvider.");
+ }
+
+ return null;
+ }
+
+ };
+ }
+
+
+}
diff --git a/src/card/omapi/impl/OmapiSecretFetcherImpl.java b/src/card/omapi/impl/OmapiSecretFetcherImpl.java
new file mode 100644
index 0000000..8d77a79
--- /dev/null
+++ b/src/card/omapi/impl/OmapiSecretFetcherImpl.java
@@ -0,0 +1,245 @@
+/*!
+ * \copyright Copyright (c) 2019 Governikus GmbH & Co. KG, Germany
+ *
+ * Copyright 2017-2019 adesso AG
+ *
+ * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by the
+ * European Commission - subsequent versions of the EUPL (the "Licence"); You may
+ * not use this work except in compliance with the Licence.
+ *
+ * You may obtain a copy of the Licence at:
+ * https://joinup.ec.europa.eu/software/page/eupl
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed
+ * under the Licence is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the Licence for the
+ * specific language governing permissions and limitations under the Licence.
+ */
+
+package com.governikus.ausweisapp2.omapi.impl;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.*;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.widget.Toast;
+import net.vx4.lib.omapi.Hex;
+import net.vx4.lib.omapi.OMAPITP;
+
+/**
+ * This class is a pretty "raw" stub to request a screen unlock to provide a fixed "secret" unlock key for the UICC
+ * applet. As the UICC is fixed in the mobile phone there is no real encryption need. But in general we want to enable
+ * a protocol similar to PACE (Password Authenticated Connection Establishment) for user authentication. As there are
+ * ideas to enable multiple identities on one or several (remember dual SIM phones) UICCs we stick to a fixed secret
+ * here until a reliable management of "CHANGE SECRET", recovery codes and hardware key storage using KeyGuard is
+ * defined and supported by the GUI. Also there are some compatibility quirks below we didn't want to throw away as
+ * long as test mobile phones still rely on them.
+ *
+ * @author kahlo
+ */
+public final class OmapiSecretFetcherImpl extends Activity
+{
+ private static final String LOG_TAG = "AusweisApp2.OMAPI";
+
+
+ /**
+ * Used to build and start the enclosing activity requiring device unlock.
+ */
+ static final class Builder
+ {
+ private final Context context;
+
+ /**
+ *
+ * @param context
+ */
+ Builder(final Context context)
+ {
+ this.context = context;
+ }
+
+
+ /**
+ * Set the callback handler, which is created inline below.
+ *
+ * @param tp
+ */
+ final void set(final OMAPITP tp)
+ {
+ System.out.println("Builder.set");
+ System.out.println("tp = [" + tp + "]");
+
+ if (tp != null && tp.getClass() == OMAPITP.class && tp.getClass().getProtectionDomain() == this.getClass().getProtectionDomain())
+ {
+ tp.setCallbackHandler(new OMAPITP.CallbackHandler(){
+ @Override
+ public byte[] getSecret(){
+ System.out.println("Builder.getSecret");
+
+ final Class< ? >[] context = new SecurityManager() {
+ @Override
+ public Class< ? >[] getClassContext(){
+ return super.getClassContext();
+ }
+ }
+ .getClassContext();
+
+ System.out.println("context = " + context); // is null on Android
+ //System.out.println(context[2]);
+ //System.out.println(context[3]);
+
+ SecurityException se = new SecurityException();
+ if (!tp.getClass().getName().equals(se.getStackTrace()[2].getClassName()))
+ {
+ throw se;
+ }
+ final Context ctx = Builder.this.context;
+
+ // TODO: check for call-source
+
+ final SharedPreferences pref = ctx.getSharedPreferences("IVID", MODE_PRIVATE);
+ final String tmwRawData = pref.getString("rawData", null);
+
+ // if(Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ // Toast.makeText(ctx, "Android API Version >= 23 required.", Toast.LENGTH_LONG);
+ // return null;
+ // };
+
+ final Object fpm = ctx.getSystemService(Context.FINGERPRINT_SERVICE);
+ if (fpm != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ((FingerprintManager) fpm).isHardwareDetected())
+ {
+ // fingerprint auth
+ // ((FingerprintManager) fpm).authenticate();
+ final FingerprintManager fingerprintManager = (FingerprintManager) fpm;
+ if (!fingerprintManager.hasEnrolledFingerprints())
+ {
+ // Toast.makeText(ctx, "No fingerprints enrolled.", Toast.LENGTH_LONG);
+ }
+ else
+ {
+
+ }
+ }
+ else
+ {
+ // fallback to KeyGuard
+
+ }
+
+ final KeyguardManager kgm = (KeyguardManager) ctx.getSystemService(Context.KEYGUARD_SERVICE);
+ Class< ? extends Activity> clazz = OmapiSecretFetcherImpl.class;
+
+ final Object[] res = new Object[1];
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || kgm.isDeviceSecure())
+ {
+ ctx.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent){
+ System.out.println("OmapiImpl.onReceive");
+ System.out.println("context = [" + context + "], intent = [" + intent + "]");
+ synchronized (res){
+ res[0] = ((Object[]) intent.getExtras().get("result"))[0];
+ res.notifyAll();
+ }
+ }
+ }, new IntentFilter(clazz.getName()));
+
+ ctx.startActivity(new Intent(ctx, clazz).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra("result", res));
+
+ synchronized (res){
+ try
+ {
+ res.wait();
+ }
+ catch (InterruptedException e)
+ {
+ }
+ }
+
+ if (res[0] == null)
+ {
+ System.out.println("RESULT: null - NEGATIVE");
+ return null;
+ }
+ else
+ {
+ System.out.println("RESULT: " + res[0] + " - POSITIVE");
+
+ // TODO: use real secret
+ final byte[] ulk = Hex.x("E813C3318BBA3958D5ABD2E1D0B3163A");
+
+ return ulk;
+ }
+ }
+
+ return null;
+ }
+ });
+ }
+ }
+
+
+ }
+
+ /**
+ *
+ * @param savedInstanceState
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+
+ System.out.println("OmapiActivity.onCreate");
+ System.out.println("savedInstanceState = [" + savedInstanceState + "]");
+
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
+ {
+ setResult(RESULT_CANCELED, null);
+ // this.sendBroadcast(new Intent(this.getClass().getName()).putExtra("result", new Object[1]));
+
+ this.sendBroadcast(new Intent(this.getClass().getName()).putExtra("result", new Object[] {""}));
+
+ finish();
+ }
+ else
+ {
+ final KeyguardManager kgm = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+ final Intent i = kgm.createConfirmDeviceCredentialIntent("meID", "meID requires valid user authorization.");
+ this.startActivityForResult(i, 1);
+ }
+ }
+
+
+ /**
+ *
+ * @param requestCode
+ * @param resultCode
+ * @param data
+ */
+ @Override
+ protected void onActivityResult(final int requestCode, final int resultCode, final Intent data)
+ {
+ System.out.println("OmapiActivity.onActivityResult");
+ System.out.println("requestCode = [" + requestCode + "], resultCode = [" + resultCode + "], data = [" + data + "]");
+
+ switch (requestCode)
+ {
+ case 1:
+ setResult(resultCode, data);
+ if (resultCode == RESULT_OK)
+ {
+ this.sendBroadcast(new Intent(this.getClass().getName()).putExtra("result", new Object[] {""}));
+ }
+ finish();
+ break;
+ }
+
+ this.sendBroadcast(new Intent(this.getClass().getName()).putExtra("result", new Object[1]));
+ finish();
+ }
+
+
+}
diff --git a/src/card/omapi/metadata.json b/src/card/omapi/metadata.json
new file mode 100644
index 0000000..8e18fcf
--- /dev/null
+++ b/src/card/omapi/metadata.json
@@ -0,0 +1,4 @@
+{
+ "name" : "OmapiReaderManagerPlugIn",
+ "dependencies" : []
+}
diff --git a/src/core/states/StateConnectCard.cpp b/src/core/states/StateConnectCard.cpp
index e9098cd..e0c9540 100644
--- a/src/core/states/StateConnectCard.cpp
+++ b/src/core/states/StateConnectCard.cpp
@@ -23,8 +23,6 @@ void StateConnectCard::run()
const auto readerManager = Env::getSingleton();
mConnections += connect(readerManager, &ReaderManager::fireCardInserted, this, &StateConnectCard::onCardInserted);
mConnections += connect(readerManager, &ReaderManager::fireReaderRemoved, this, &StateConnectCard::onReaderRemoved);
- mConnections += connect(getContext().data(), &WorkflowContext::fireAbortCardSelection, this, &StateConnectCard::onAbort);
- mConnections += connect(getContext().data(), &WorkflowContext::fireReaderPlugInTypesChanged, this, &StateConnectCard::fireRetry);
onCardInserted();
}
@@ -82,3 +80,20 @@ void StateConnectCard::onAbort()
}
Q_EMIT fireRetry();
}
+
+
+void StateConnectCard::onEntry(QEvent* pEvent)
+{
+ const WorkflowContext* const context = getContext().data();
+ Q_ASSERT(context);
+
+ mConnections += connect(context, &WorkflowContext::fireAbortCardSelection, this, &StateConnectCard::onAbort);
+
+ /*
+ * Note: the plugin types to be used in this state must be already set in the workflow context before this state is entered.
+ * Changing the plugin types in the context, e.g. from {NFC} to {BLUETOOTH}, causes the state to be left with a fireRetry signal.
+ */
+ mConnections += connect(context, &WorkflowContext::fireReaderPlugInTypesChanged, this, &StateConnectCard::fireRetry);
+
+ AbstractGenericState::onEntry(pEvent);
+}
diff --git a/src/core/states/StateConnectCard.h b/src/core/states/StateConnectCard.h
index 940275c..2f98788 100644
--- a/src/core/states/StateConnectCard.h
+++ b/src/core/states/StateConnectCard.h
@@ -28,6 +28,9 @@ class StateConnectCard
void onReaderRemoved(const QString& pReaderName);
void onAbort();
+ public:
+ void onEntry(QEvent* pEvent) override;
+
Q_SIGNALS:
void fireRetry();
void fireReaderRemoved();
diff --git a/src/core/states/StateSelectReader.cpp b/src/core/states/StateSelectReader.cpp
index 0d054c3..5c7b0d1 100644
--- a/src/core/states/StateSelectReader.cpp
+++ b/src/core/states/StateSelectReader.cpp
@@ -24,7 +24,8 @@ StateSelectReader::StateSelectReader(const QSharedPointer& pCon
bool StateSelectReader::requiresCard(ReaderManagerPlugInType pPlugInType)
{
return pPlugInType == ReaderManagerPlugInType::PCSC ||
- pPlugInType == ReaderManagerPlugInType::REMOTE;
+ pPlugInType == ReaderManagerPlugInType::REMOTE ||
+ pPlugInType == ReaderManagerPlugInType::OMAPI;
}
diff --git a/src/global/LogCategories.cpp b/src/global/LogCategories.cpp
index 373512b..48d0f63 100644
--- a/src/global/LogCategories.cpp
+++ b/src/global/LogCategories.cpp
@@ -12,6 +12,7 @@ Q_LOGGING_CATEGORY(bluetooth, "bluetooth")
Q_LOGGING_CATEGORY(card_pcsc, "card_pcsc")
Q_LOGGING_CATEGORY(card_nfc, "card_nfc")
Q_LOGGING_CATEGORY(card_remote, "card_remote")
+Q_LOGGING_CATEGORY(card_omapi, "card_omapi")
Q_LOGGING_CATEGORY(remote_device, "remote_device")
Q_LOGGING_CATEGORY(card_drivers, "card_drivers")
Q_LOGGING_CATEGORY(statemachine, "statemachine")
diff --git a/src/main.cpp b/src/main.cpp
index 837c30a..99d739b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -52,6 +52,7 @@ Q_IMPORT_PLUGIN(NfcReaderManagerPlugIn)
#ifndef ANDROID_BUILD_AAR
Q_IMPORT_PLUGIN(IntentActivationHandler)
+Q_IMPORT_PLUGIN(OmapiReaderManagerPlugIn)
#endif
#endif