- General configuration
- DeepLink Schemas configuration
- Scoped Issuance Document Configuration
- How to work with self-signed certificates
- Theme configuration
- Pin Storage configuration
- Analytics 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: VciConfig { get }
}
Based on the Build Variant of the Wallet (e.g., Dev)
struct WalletKitConfigImpl: WalletKitConfig {
let configLogic: ConfigLogic
init(configLogic: ConfigLogic) {
self.configLogic = configLogic
}
var vciConfig: VciConfig {
return switch configLogic.appBuildVariant {
case .DEMO:
.init(
issuerUrl: "your_demo_url",
clientId: "your_demo_clientid",
redirectUri: URL(string: "your_demo_redirect")!
)
case .DEV:
.init(
issuerUrl: "your_dev_url",
clientId: "your_dev_clientid",
redirectUri: URL(string: "your_dev_redirect")!
)
}
}
}
- 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.
The application's IACA certificates are located here
var readerConfigConfig: ReaderConfig {
guard let cert = Data(name: "eudi_pid_issuer_ut", ext: "der") else {
return .init(trustedCerts: [])
}
return .init(trustedCerts: [cert])
}
- Preregistered Client Scheme
If you plan to use the Preregistered for OpenId4VP configuration, please add the following to the WalletKitConfigImpl initializer.
wallet.verifierApiUri = "your_verifier_url"
wallet.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
var rQESConfig: RqesServiceConfig
// 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",
uri: URL(string: "your_dev_uri")!,
scaURL: "your_dev_sca"
)
]
case .DEMO:
[
.init(
name: "your_demo_name",
uri: URL(string: "your_demo_uri")!,
scaURL: "your_demo_sca"
)
]
}
}
var printLogs: Bool {
buildType == .DEBUG
}
var rQESConfig: RqesServiceConfig {
return switch buildVariant {
case .DEV:
.init(
clientId: "your_dev_clientid",
clientSecret: "your_dev_secret",
authFlowRedirectionURI: "your_dev_redirect",
hashAlgorithm: .SHA256
)
case .DEMO:
.init(
clientId: "your_demo_clientid",
clientSecret: "your_demo_secret",
authFlowRedirectionURI: "your_demo_redirect",
hashAlgorithm: .SHA256
)
}
}
}
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>openid-credential-offer</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>openid-credential-offer</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 credential_offer
case rqes
case external
static func parseType(
with scheme: String,
and urlSchemaController: UrlSchemaController
) -> Action? {
switch scheme {
case _ where openid4vp.getSchemas(with: urlSchemaController).contains(scheme):
return .openid4vp
case _ where credential_offer.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 credential_offer
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):
return .openid4vp
case _ where credential_offer.getSchemas(with: urlSchemaController).contains(scheme):
return .credential_offer
case _ 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
}
}
}
}
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.
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.
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, add the following:
wallet.urlSession = walletSession
in the initializer. This change will allow the app to interact with web services that rely on self-signed certificates.
The application allows the configuration of:
- Colors
- Images
- Shape
- Fonts
- Dimension
Via the ThemeConfiguration struct.
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)
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 provide your implementation by implementing the AnalyticsProvider protocol and then adding it to your AnalyticsConfigImpl analyticsProviders variable. You will also need the provider's token/key, thus requiring a [String: AnalyticsProvider] configuration. The project utilizes Dependency Injection (DI), thus requiring adjustment of 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)