Skip to content

Commit

Permalink
fix #97; check userAuthenticationRequired and useStrongBoxForKeys whe…
Browse files Browse the repository at this point in the history
…n building EudiWallet and unset if either one is not supported; removed unused code
  • Loading branch information
vkanellopoulos committed Nov 22, 2024
1 parent c54f576 commit 60f5662
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,21 @@ values are used to create the eu.europa.ec.eudi.wallet.document.CreateDocumentSe
using [eu.europa.ec.eudi.wallet.document.DocumentExtensions.getDefaultCreateDocumentSettings](../../eu.europa.ec.eudi.wallet.document/-document-extensions/get-default-create-document-settings.md)
method.

If user authentication is required, the user authentication timeout must be greater than 0.

**Note**: when setting userAuthenticationRequired to true, device must be secured with a PIN,
pattern or password.

**Note**: when setting useStrongBoxForKeys to true, the device must support the StrongBox.

The default values are:

-
userAuthenticationRequired: false
-
userAuthenticationTimeout: 0
-
useStrongBoxForKeys: true
useStrongBoxForKeys: true if supported by the device

#### Parameters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,42 @@ fun [build](build.md)(): [EudiWallet](../index.md)

Build the [EudiWallet](../index.md) instance

#### Return
The [EudiWallet](../index.md) instance will be created based on the configuration provided in
the [Builder](index.md) class.

[EudiWallet](../index.md)
The [EudiWallet](../index.md) instance will be created with the following default implementations if
not set:

-
AndroidStorageEngine for storing/retrieving documents
-
AndroidKeystoreSecureArea for managing documents' keys
-
DocumentManagerImpl for managing documents
-
[PresentationManagerImpl](../../../eu.europa.ec.eudi.wallet.presentation/-presentation-manager-impl/index.md)
for both proximity and remote presentation
-
[OpenId4VpManager](../../../eu.europa.ec.eudi.wallet.transfer.openId4vp/-open-id4-vp-manager/index.md)
for remote presentation
-
TransferManagerImpl for proximity presentation

**Note**:
The [EudiWalletConfig.documentsStorageDir](../../-eudi-wallet-config/documents-storage-dir.md) is
not set, the default storage directory will be used which is the application's no backup files
directory.

#### Throws
**Note**:
The [EudiWalletConfig.userAuthenticationRequired](../../-eudi-wallet-config/user-authentication-required.md)
is set to true and the device is not secured with a PIN, pattern, or password, the configuration
will be updated to set the user authentication required to false.

| | |
|------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [IllegalStateException](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-illegal-state-exception/index.html) | if [EudiWalletConfig.documentsStorageDir](../../-eudi-wallet-config/documents-storage-dir.md) is not set and and the default DocumentManager is going to be used |
**Note**:
The [EudiWalletConfig.useStrongBoxForKeys](../../-eudi-wallet-config/use-strong-box-for-keys.md) is
set to true and the device does not support StrongBox, the configuration will be updated to set the
use StrongBox for keys to false.

#### Return

[EudiWallet](../index.md)
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ systemProp.sonar.host.url=https://sonarcloud.io
systemProp.sonar.gradle.skipCompile=true
systemProp.sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/testDebugUnitTestCoverage/testDebugUnitTestCoverage.xml,build/reports/jacoco/testReleaseUnitTestCoverage/testReleaseUnitTestCoverage.xml
systemProp.sonar.projectName=eudi-lib-android-wallet-core
VERSION_NAME=0.12.0
VERSION_NAME=0.12.1-SNAPSHOT

SONATYPE_HOST=S01
SONATYPE_AUTOMATIC_RELEASE=false
Expand Down
2 changes: 1 addition & 1 deletion licenses.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# EUDI Wallet Core library for Android
## Dependency License Report

_2024-11-20 17:38:59 EET_
_2024-11-22 13:51:07 EET_
## Apache License, Version 2.0

**1** **Group:** `androidx.appcompat` **Name:** `appcompat` **Version:** `1.6.1`
Expand Down
26 changes: 8 additions & 18 deletions wallet-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -106,50 +106,40 @@ android {

dependencies {

implementation(libs.appcompat)

// EUDI libs
api(libs.eudi.document.manager)
api(libs.eudi.iso18013.data.transfer)
// Identity android library
api(libs.google.identity.android) {
exclude(group = "org.bouncycastle")
}

implementation(libs.appcompat)
// OpenID4VCI
implementation(libs.eudi.lib.jvm.openid4vci.kt)
implementation(libs.nimbus.oauth2.oidc.sdk)

// Siop-Openid4VP library
implementation(libs.eudi.lib.jvm.siop.openid4vp.kt) {
exclude(group = "org.bouncycastle")
}

// Identity android library
api(libs.google.identity.android) {
exclude(group = "org.bouncycastle")
}

implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.io.core)
implementation(libs.kotlinx.io.bytestring)

// CBOR
implementation(libs.cbor)

testImplementation(libs.testng)

implementation(libs.upokecenter.cbor)
implementation(libs.cose.java)
// Ktor Android Engine
runtimeOnly(libs.ktor.client.android)
implementation(libs.ktor.client.logging)

// Bouncy Castle
implementation(libs.bouncy.castle.prov)
implementation(libs.bouncy.castle.pkix)

implementation(libs.upokecenter.cbor)
implementation(libs.cose.java)
runtimeOnly(libs.ktor.client.android)

testImplementation(kotlin("test"))
testImplementation(libs.mockk)
testImplementation(libs.json)
testImplementation(libs.junit.jupiter.params)
testImplementation(libs.kotlin.coroutines.test)
testImplementation(libs.biometric.ktx)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ class EudiWalletTest {
fun testLoadSampleDocuments() {
walletConfig = EudiWalletConfig()
.configureDocumentKeyCreation(
userAuthenticationRequired = false,
useStrongBoxForKeys = false,
userAuthenticationRequired = true,
userAuthenticationTimeout = 30_000L,
useStrongBoxForKeys = true,
)

val result = wallet.loadMdocSampleDocuments(
Expand Down
138 changes: 109 additions & 29 deletions wallet-core/src/main/java/eu/europa/ec/eudi/wallet/EudiWallet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ import com.android.identity.securearea.SecureArea
import com.android.identity.securearea.SecureAreaRepository
import com.android.identity.storage.StorageEngine
import eu.europa.ec.eudi.iso18013.transfer.TransferManager
import eu.europa.ec.eudi.iso18013.transfer.TransferManagerImpl
import eu.europa.ec.eudi.iso18013.transfer.engagement.BleRetrievalMethod
import eu.europa.ec.eudi.iso18013.transfer.readerauth.ReaderTrustStore
import eu.europa.ec.eudi.wallet.document.*
import eu.europa.ec.eudi.wallet.document.sample.SampleDocumentManager
import eu.europa.ec.eudi.wallet.internal.LogPrinterImpl
import eu.europa.ec.eudi.wallet.internal.i
import eu.europa.ec.eudi.wallet.issue.openid4vci.OpenId4VciManager
import eu.europa.ec.eudi.wallet.logging.Logger
import eu.europa.ec.eudi.wallet.presentation.PresentationManager
Expand Down Expand Up @@ -108,6 +110,8 @@ interface EudiWallet : SampleDocumentManager, PresentationManager {

companion object {

private const val TAG = "EudiWallet"

/**
* Create an instance of [EudiWallet] with the given configuration and additional configuration
* using the [Builder] class
Expand Down Expand Up @@ -235,16 +239,31 @@ interface EudiWallet : SampleDocumentManager, PresentationManager {
/**
* Build the [EudiWallet] instance
*
* @throws IllegalStateException if [setConfig] is not set
* @throws IllegalStateException if [EudiWalletConfig.documentsStorageDir] is not set and
* and the default [DocumentManager] is going to be used
* The [EudiWallet] instance will be created based on the configuration provided in the [Builder] class.
*
* The [EudiWallet] instance will be created with the following default implementations if not set:
* - [AndroidStorageEngine] for storing/retrieving documents
* - [AndroidKeystoreSecureArea] for managing documents' keys
* - [DocumentManagerImpl] for managing documents
* - [PresentationManagerImpl] for both proximity and remote presentation
* - [OpenId4VpManager] for remote presentation
* - [TransferManagerImpl] for proximity presentation
*
* **Note**: The [EudiWalletConfig.documentsStorageDir] is not set, the default storage directory
* will be used which is the application's no backup files directory.
*
* **Note**: The [EudiWalletConfig.userAuthenticationRequired] is set to true and the device is not secured with a PIN,
* pattern, or password, the configuration will be updated to set the user authentication required to false.
*
* **Note**: The [EudiWalletConfig.useStrongBoxForKeys] is set to true and the device does not support StrongBox,
* the configuration will be updated to set the use StrongBox for keys to false.
*
* @return [EudiWallet]
*/
fun build(): EudiWallet {

val loggerToUse = (logger ?: Logger(config)).also {
IdentityLogger.setLogPrinter(LogPrinterImpl(it))
}
ensureStrongBoxIsSupported()
ensureUserAuthIsSupported()

val documentManagerToUse =
documentManager ?: getDefaultDocumentManager(storageEngine, secureAreas)
Expand All @@ -253,31 +272,18 @@ interface EudiWallet : SampleDocumentManager, PresentationManager {

val transferManager = getTransferManager(documentManagerToUse, readerTrustStoreToUse)

val presentationManagerToUse = presentationManager ?: run {
val openId4vpManager = config.openId4VpConfig?.let { openId4VpConfig ->
OpenId4VpManager(
config = openId4VpConfig,
requestProcessor = OpenId4VpRequestProcessor(
documentManagerToUse,
readerTrustStoreToUse
),
logger = loggerToUse,
ktorHttpClientFactory = ktorHttpClientFactory
)
}
PresentationManagerImpl(
transferManager = transferManager,
openId4vpManager = openId4vpManager,
nfcEngagementServiceClass = config.nfcEngagementServiceClass,
)
}
val presentationManagerToUse = presentationManager ?: getDefaultPresentationManager(
documentManager = documentManagerToUse,
transferManager = transferManager,
readerTrustStore = readerTrustStoreToUse
)

val openId4vpManagerFactory = {
config.openId4VciConfig?.let { openId4VciConfig ->
OpenId4VciManager(context) {
documentManager(documentManagerToUse)
config(openId4VciConfig)
logger(loggerToUse)
logger(loggerObj)
ktorHttpClientFactory?.let { ktorHttpClientFactory(it) }
}
} ?: throw IllegalStateException("OpenId4Vp configuration is missing")
Expand All @@ -289,12 +295,47 @@ interface EudiWallet : SampleDocumentManager, PresentationManager {
documentManager = documentManagerToUse,
presentationManager = presentationManagerToUse,
transferManager = transferManager,
logger = loggerToUse,
logger = loggerObj,
readerTrustStoreConsumer = { presentationManagerToUse.readerTrustStore = it },
openId4VciManagerFactory = openId4vpManagerFactory,
)
}

/**
* Get the default [PresentationManagerImpl] instance based on the [DocumentManager] and [TransferManager] implementations
* @param documentManager the document manager
* @param transferManager the transfer manager
* @param readerTrustStore the reader trust store
* @return the default [PresentationManagerImpl] instance
*/
@JvmSynthetic
internal fun getDefaultPresentationManager(
documentManager: DocumentManager,
transferManager: TransferManager,
readerTrustStore: ReaderTrustStore?,
): PresentationManagerImpl {
val openId4vpManager = config.openId4VpConfig?.let { openId4VpConfig ->
OpenId4VpManager(
config = openId4VpConfig,
requestProcessor = OpenId4VpRequestProcessor(documentManager, readerTrustStore),
logger = loggerObj,
ktorHttpClientFactory = ktorHttpClientFactory
)
}
return PresentationManagerImpl(
transferManager = transferManager,
openId4vpManager = openId4vpManager,
nfcEngagementServiceClass = config.nfcEngagementServiceClass,
)
}

@get:JvmSynthetic
internal val loggerObj: Logger by lazy {
(logger ?: Logger(config)).also {
IdentityLogger.setLogPrinter(LogPrinterImpl(it))
}
}

@get:JvmSynthetic
internal val defaultStorageDir: Path
get() = Path(
Expand Down Expand Up @@ -336,9 +377,9 @@ interface EudiWallet : SampleDocumentManager, PresentationManager {
return AndroidStorageEngine.Builder(
context = context,
storageFile = documentsStorageDirToUse
).apply {
setUseEncryption(config.encryptDocumentsInStorage)
}.build()
)
.setUseEncryption(config.encryptDocumentsInStorage)
.build()
}

/**
Expand Down Expand Up @@ -400,5 +441,44 @@ interface EudiWallet : SampleDocumentManager, PresentationManager {
)
)
)

/**
* Returns the capabilities of the Android Keystore Secure Area
*/
@get:JvmSynthetic
internal val capabilities: AndroidKeystoreSecureArea.Capabilities by lazy {
AndroidKeystoreSecureArea.Capabilities(context)
}

/**
* Check if user authentication is required and update the configuration
* if the device is not secure and the configuration is set to require user authentication
*/
@JvmSynthetic
internal fun ensureUserAuthIsSupported() {
if (capabilities.secureLockScreenSetup.not() && config.userAuthenticationRequired) {
loggerObj.i(
TAG,
"""User authentication is required but the device is not secured with a PIN, pattern, or password.
| Setting EudiWalletConfig.userAuthenticationRequired to false.""".trimMargin()
)
config.userAuthenticationRequired = false
}
}

/**
* Check if StrongBox is supported on the device and update the configuration
* if it is not supported and the configuration is set to use StrongBox
*/
@JvmSynthetic
internal fun ensureStrongBoxIsSupported() {
if (capabilities.strongBoxSupported.not() && config.useStrongBoxForKeys) {
loggerObj.i(
TAG, """StrongBox is not supported on this device.
| Setting EudiWalletConfig.useStrongBoxForKeys to false.""".trimMargin()
)
config.useStrongBoxForKeys = false
}
}
}
}
Loading

0 comments on commit 60f5662

Please sign in to comment.