How to configure the application
Table of contents
- General configuration
- DeepLink Schemas configuration
- Scoped Issuance Document Configuration
- How to work with self-signed certificates
- Theme configuration
- Pin Storage configuration
- Analytics configuration
General configuration
The application allows the configuration of:
- Issuing API
Via the WalletKitConfig protocol inside the logic-core module.
public protocol WalletKitConfig {
/**
* VCI Configuration
*/
var vciConfig: [String: OpenId4VciConfiguration] { get }
}
struct WalletKitConfigImpl: WalletKitConfig {
let configLogic: ConfigLogic
init(configLogic: ConfigLogic) {
self.configLogic = configLogic
}
var vciConfig: [String: OpenId4VciConfiguration] {
let openId4VciConfigurations: [OpenId4VciConfiguration] = {
switch configLogic.appBuildVariant {
case .DEMO:
return [
.init(
credentialIssuerURL: "your_demo_issuer_url",
clientId: "your_demo_client_id_or_nil",
keyAttestationsConfig: .init(walletAttestationsProvider: walletKitAttestationProvider),
authFlowRedirectionURI: URL(string: "your_demo_redirect")!,
usePAR: should_use_par_bool,
useDPoP: should_use_dpop_bool
useDpopIfSupported: should_use_dpop_bool,
cacheIssuerMetadata: should_cache_metadata_bool
)
]
case .DEV:
return [
.init(
credentialIssuerURL: "your_dev_issuer_url",
clientId: "your_dev_client_id_or_nil",
keyAttestationsConfig: .init(walletAttestationsProvider: walletKitAttestationProvider),
authFlowRedirectionURI: URL(string: "your_dev_redirect")!,
usePAR: should_use_par_bool,
useDpopIfSupported: should_use_dpop_bool,
cacheIssuerMetadata: should_cache_metadata_bool
)
]
}
}()
// ...
}
}
- Wallet Attestation Provider
Via the WalletKitAttestationConfig protocol inside the logic-core module.
protocol WalletProviderAttestationConfig: Sendable {
var walletProviderAttestationUrl: String { get }
}
Based on the Build Variant of the Wallet (e.g., Dev)
final class WalletProviderAttestationConfigImpl: WalletProviderAttestationConfig {
let configLogic: ConfigLogic
init(configLogic: ConfigLogic) {
self.configLogic = configLogic
}
var walletProviderAttestationUrl: String {
switch configLogic.appBuildVariant {
case .DEMO:
"your_demo_wallet_provider_host"
case .DEV:
"your_dev_wallet_provider_host"
}
}
}
- Trusted certificates
Via the WalletKitConfig protocol inside the logic-core module.
public protocol WalletKitConfig {
/**
* Reader Configuration
*/
var readerConfig: ReaderConfig { get }
}
public struct ReaderConfig {
public let trustedCerts: [Data]
}
The WalletKitConfigImpl implementation of the WalletKitConfig protocol can be located inside the logic-core module.
var readerConfigConfig: ReaderConfig {
guard let cert = Data(name: "eudi_pid_issuer_ut", ext: "der") else {
return .init(trustedCerts: [])
}
return .init(trustedCerts: [cert])
}
- VP API
Via the WalletKitConfig protocol inside the logic-core module.
public protocol WalletKitConfig {
/**
* VP Configuration
*/
var vpConfig: OpenId4VpConfiguration { get }
}
The preregistered scheme is optional. If you want to use it, please add the following: the SiopOpenID4VP import and the .preregistered option in the clientIdSchemes array.
import SiopOpenID4VP
struct WalletKitConfigImpl: WalletKitConfig {
let configLogic: ConfigLogic
init(configLogic: ConfigLogic) {
self.configLogic = configLogic
}
var vpConfig: OpenId4VpConfiguration {
.init(
clientIdSchemes: [
.x509SanDns,
.x509Hash,
.preregistered(
[
PreregisteredClient(
clientId: "your_verifier_id",
verifierApiUri: "your_verifier_url",
verifierLegalName: "your_verifier_legal_name"
)
]
)
]
)
}
}
- RQES
Via the RQESConfig struct, which implements the EudiRQESUiConfig protocol from the RQESUi SDK, inside the logic-business module.
final class RQESConfig: EudiRQESUiConfig {
let buildVariant: AppBuildVariant
let buildType: AppBuildType
init(buildVariant: AppBuildVariant, buildType: AppBuildType) {
self.buildVariant = buildVariant
self.buildType = buildType
}
var rssps: [QTSPData]
// Optional. Default is false.
var printLogs: Bool
// Optional. Default English translations will be used if not set.
var translations: [String : [LocalizableKey : String]]
// Optional. Default theme will be used if not set.
var theme: ThemeProtocol
}
Based on the Build Variant and Type of the Wallet (e.g., Dev Debug)
final class RQESConfig: EudiRQESUiConfig {
let buildVariant: AppBuildVariant
let buildType: AppBuildType
init(buildVariant: AppBuildVariant, buildType: AppBuildType) {
self.buildVariant = buildVariant
self.buildType = buildType
}
var rssps: [QTSPData] {
return switch buildVariant {
case .DEV:
[
.init(
name: "your_dev_name",
rsspId: "your_dev_rssp",
tsaUrl: "your_dev_tsa",
clientId: "your_dev_clientid",
clientSecret: "your_dev_secret",
authFlowRedirectionURI: "your_dev_redirect",
hashAlgorithm: .SHA256,
includeRevocationInfo: false
)
]
case .DEMO:
[
.init(
name: "your_demo_name",
rsspId: "your_demo_rssp",
tsaUrl: "your_demo_tsa",
clientId: "your_demo_clientid",
clientSecret: "your_demo_secret",
authFlowRedirectionURI: "your_demo_redirect",
hashAlgorithm: .SHA256,
includeRevocationInfo: false
)
]
}
}
var printLogs: Bool {
buildType == .DEBUG
}
}
DeepLink Schemas configuration
According to the specifications, issuance, presentation, and RQES require deep-linking for the same device flows.
If you want to change or add your own, you can do it by adjusting the Wallet.plist file.
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleURLSchemes</key>
<array>
<string>eudi-openid4vp</string>
<string>mdoc-openid4vp</string>
<string>openid4vp</string>
<string>haip-vp</string>
<string>openid-credential-offer</string>
<string>haip-vci</string>
<string>rqes</string>
</array>
</dict>
</array>
Let's assume you want to add a new one for the credential offer (e.g., custom-my-offer://), the Wallet.plist should look like this:
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleURLSchemes</key>
<array>
<string>eudi-openid4vp</string>
<string>mdoc-openid4vp</string>
<string>openid4vp</string>
<string>haip-vp</string>
<string>openid-credential-offer</string>
<string>haip-vci</string>
<string>rqes</string>
<string>custom-my-offer</string>
</array>
</dict>
</array>
After the Wallet.plist adjustment, you must also adjust the DeepLinkController inside the logic-ui module.
Current Implementation:
public extension DeepLink {
enum Action: String, Equatable {
case openid4vp
case haip_vp
case credential_offer
case haip_vci
case rqes
case external
static func parseType(
with scheme: String,
and urlSchemaController: UrlSchemaController
) -> Action? {
switch scheme {
case _ where openid4vp.getSchemas(with: urlSchemaController).contains(scheme),
_ where haip_vp.getSchemas(with: urlSchemaController).contains(scheme):
return .openid4vp
case _ where credential_offer.getSchemas(with: urlSchemaController).contains(scheme),
_ where haip_vci.getSchemas(with: urlSchemaController).contains(scheme):
return .credential_offer
case _ where rqes.getSchemas(with: urlSchemaController).contains(scheme):
return .rqes
default:
return .external
}
}
}
}
Adjusted with the new schema:
public extension DeepLink {
enum Action: String, Equatable {
case openid4vp
case haip_vp
case credential_offer
case haip_vci
case rqes
case custom_my_offer
case external
static func parseType(
with scheme: String,
and urlSchemaController: UrlSchemaController
) -> Action? {
switch scheme {
case _ where openid4vp.getSchemas(with: urlSchemaController).contains(scheme),
_ where haip_vp.getSchemas(with: urlSchemaController).contains(scheme):
return .openid4vp
case _ where credential_offer.getSchemas(with: urlSchemaController).contains(scheme),
_ where haip_vci.getSchemas(with: urlSchemaController).contains(scheme),
_ where custom_my_offer.getSchemas(with: urlSchemaController).contains(scheme):
return .credential_offer
case _ where rqes.getSchemas(with: urlSchemaController).contains(scheme):
return .rqes
default:
return .external
}
}
}
}
Scoped Issuance Document Configuration
The credential configuration is derived directly from the issuer's metadata. The issuer URL is configured via the VciConfig property inside the logic-core module in WalletKitConfigImpl. If you want to add or adjust the displayed scoped documents, you must modify the issuer's metadata, and the wallet will automatically resolve your changes.
How to work with self-signed certificates
This section describes configuring the application to interact with services utilizing self-signed certificates.
Add these lines of code to the top of the file WalletKitController, inside the logic-core module, just below the import statements.
final class SelfSignedDelegate: NSObject, URLSessionDelegate {
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
// Check if the challenge is for a self-signed certificate
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let trust = challenge.protectionSpace.serverTrust {
// Create a URLCredential with the self-signed certificate
let credential = URLCredential(trust: trust)
// Call the completion handler with the credential to accept the self-signed certificate
completionHandler(.useCredential, credential)
} else {
// For other authentication methods, call the completion handler with a nil credential to reject the request
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
}
let walletSession: URLSession = {
let delegate = SelfSignedDelegate()
let configuration = URLSessionConfiguration.default
return URLSession(
configuration: configuration,
delegate: delegate,
delegateQueue: nil
)
}()
Once the above is in place, adjust the initializer:
guard let walletKit = try? EudiWallet(serviceName: configLogic.documentStorageServiceName, networking: walletSession) else {
fatalError("Unable to Initialize WalletKit")
}
This change will allow the app to interact with web services that rely on self-signed certificates.
Theme configuration
The application allows the configuration of:
- Colors
- Images
- Shape
- Fonts
- Dimension
Via the ThemeConfiguration struct.
Pin Storage configuration
The application allows the configuration of the PIN storage. You can configure the following:
- Where the pin will be stored
- From where the pin will be retrieved
- Pin matching and validity
Via the LogicAuthAssembly inside the logic-authentication module.
public final class LogicAuthAssembly: Assembly {
public init() {}
public func assemble(container: Container) {
}
}
You can provide your storage implementation by implementing the PinStorageProvider protocol and then providing the implementation inside the Assembly DI Graph LogicAuthAssembly
Implementation Example:
final class KeychainPinStorageProvider: PinStorageProvider {
private let keyChainController: KeyChainController
init(keyChainController: KeyChainController) {
self.keyChainController = keyChainController
}
func retrievePin() -> String? {
keyChainController.getValue(key: KeychainIdentifier.devicePin)
}
func setPin(with pin: String) {
keyChainController.storeValue(key: KeychainIdentifier.devicePin, value: pin)
}
func isPinValid(with pin: String) -> Bool {
keyChainController.getValue(key: KeychainIdentifier.devicePin) == pin
}
}
Config Example:
container.register(PinStorageProvider.self) { r in
KeychainPinStorageProvider(keyChainController: r.force(KeyChainController.self))
}
.inObjectScope(ObjectScope.graph)
Analytics configuration
The application allows the configuration of multiple analytics providers. You can configure the following:
- Initializing the provider (e.g., Firebase, Appcenter, etc)
- Screen logging
- Event logging
Via the AnalyticsConfig and LogicAnalyticsAssembly inside the logic-analytics module.
protocol AnalyticsConfig {
/**
* Supported Analytics Provider, e.g. Firebase
*/
var analyticsProviders: [String: AnalyticsProvider] { get }
}
You can implement the AnalyticsProvider protocol and add it to your AnalyticsConfigImpl analyticsProviders variable. You will also need the provider's token/key, thus requiring a [String: AnalyticsProvider] configuration. The project uses Dependency Injection (DI), which requires adjusting the LogicAnalyticsAssembly graph to provide the configuration.
Implementation Example:
struct AppCenterProvider: AnalyticsProvider {
func initialize(key: String) {
AppCenter.start(
withAppSecret: key,
services: [
Analytics.self
]
)
}
func logScreen(screen: String, arguments: [String: String]) {
if Analytics.enabled {
logEvent(event: screen, arguments: arguments)
}
}
func logEvent(event: String, arguments: [String: String]) {
Analytics.trackEvent(event, withProperties: arguments)
}
}
Config Example:
struct AnalyticsConfigImpl: AnalyticsConfig {
var analyticsProviders: [String: AnalyticsProvider] {
return ["YOUR_OWN_KEY": AppCenterProvider()]
}
}
Config Construction via DI Graph Example:
container.register(AnalyticsConfig.self) { _ in
AnalyticsConfigImpl()
}
.inObjectScope(ObjectScope.graph)