Requirements
BSI-eRp-ePA
O.Arch_1
See external SSDLC documentation.
O.Arch_2
Data storage is divided into service driven data that is stored within a database, user driven data that must be secured, such as the eGK CAN or the eGK Certificate and user specific data that is not critical, such as already seen tooltips or preferences.
../Sources/eRpLocalStorage/CoreDataControllerFactory.swift:32
CoreData databases are protected
// [REQ:BSI-eRp-ePA:O.Purp_8#2,O.Arch_2#2] CoreData databases are protected
self.fileProtection = fileProtection
../Sources/eRpLocalStorage/CoreDataController.swift:60
actual protection setting on file system level
// [REQ:BSI-eRp-ePA:O.Purp_8#3,O.Arch_2#3] actual protection setting on file system level
protectedStoreDescription.setOption(
../Sources/eRpLocalStorage/CoreDataController.swift:84
Database backup exclusion
// [REQ:BSI-eRp-ePA:O.Purp_8#4,O.Arch_2#4,O.Arch_4#2,O.Data_15#1] Database backup exclusion
if excludeFromBackup, let storeUrl = store.url {
../Sources/eRpApp/Session/KeychainStorage.swift:23
Implementation of data storage that is
// [REQ:gemSpec_eRp_FdV:A_19186]
// [REQ:gemSpec_eRp_FdV:A_19188] Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_IDP_Frontend:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemF_Tokenverschlüsselung:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Implementation
// [REQ:BSI-eRp-ePA:O.Purp_8#1,O.Arch_2#5,O.Arch_4#3,O.Source_7#2,O.Data_2#2] Implementation of data storage that is
// persisted via keychain
class KeychainStorage: SecureUserDataStore, IDPStorage, SecureEGKCertificateStorage {
../Sources/eRpLocalStorage/UserStore/UserDefaultsStore.swift:12
User data that has no password character
/// Interface to access user specific data
/// [REQ:BSI-eRp-ePA:O.Arch_2#6] User data that has no password character
public class UserDefaultsStore: UserDataStore {
O.Arch_3
All cryptography is specified by gemSpec_Krypt in corporation with BSI
O.Arch_4
All data is either stored encrypted within the keychain or excluded from system backup, which also excludes files from cloud backup.
../Sources/eRpLocalStorage/CoreDataController.swift:84
Database backup exclusion
// [REQ:BSI-eRp-ePA:O.Purp_8#4,O.Arch_2#4,O.Arch_4#2,O.Data_15#1] Database backup exclusion
if excludeFromBackup, let storeUrl = store.url {
../Sources/eRpApp/Session/KeychainStorage.swift:23
Implementation of data storage that is
// [REQ:gemSpec_eRp_FdV:A_19186]
// [REQ:gemSpec_eRp_FdV:A_19188] Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_IDP_Frontend:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemF_Tokenverschlüsselung:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Implementation
// [REQ:BSI-eRp-ePA:O.Purp_8#1,O.Arch_2#5,O.Arch_4#3,O.Source_7#2,O.Data_2#2] Implementation of data storage that is
// persisted via keychain
class KeychainStorage: SecureUserDataStore, IDPStorage, SecureEGKCertificateStorage {
O.Arch_5
CAN and PIN verification is not done within the application but on the eGK chip. Actual authentication by confirming the eGK signature and validating the eGK certificate is done by the IDP. Authorization of users is done by the FD by validating the Access-Token signature.
O.Arch_6
Apple already implements this with a signed binary delivered to customers. An altered application can only run on a jailbroken device. If a user is using a jailbroken device, may it be known or unknown, we display a security alert so a user can make an informed decision to use or not use the application.
../Sources/eRpApp/Screens/Main/MainView.swift:120
trigger device security check
// [REQ:BSI-eRp-ePA:O.Arch_6#2,O.Resi_2#2,O.Plat_1#2] trigger device security check
viewStore.send(.loadDeviceSecurityView)
../Sources/eRpApp/Session/Helper/DeviceSecurityManager.swift:58
calculate system risk for jailbreak and missing device pin
// [REQ:BSI-eRp-ePA:O.Arch_6#3,O.Resi_2#3,O.Plat_1#3] calculate system risk for jailbreak and missing device pin
var showSystemSecurityWarning: AnyPublisher<DeviceSecurityWarningType, Never> {
../Sources/eRpApp/Session/Helper/DeviceSecurityManager.swift:112
Jailbreak detection
// [REQ:BSI-eRp-ePA:O.Arch_6#4,O.Resi_2#4] Jailbreak detection
func informJailbreakDetected() -> Bool {
../Sources/eRpApp/Screens/Main/DeviceSecurity/DeviceSecurityRootedDevice/DeviceSecurityRootedDeviceView.swift:8
Jailbreak information view
// [REQ:BSI-eRp-ePA:O.Arch_6#5,O.Resi_2#5] Jailbreak information view
struct DeviceSecurityRootedDeviceView: View {
O.Arch_7
See Source/eRpApp/Resources/en.lproj/FOSS.html
for library usage purposes .
O.Arch_8
../Sources/eRpApp/Screens/Settings/TermsOfUse/TermsOfUseView.swift:12
Webview containint local html without javascript
// [REQ:BSI-eRp-ePA:O.Purp_3#2] Actual view for the Terms of Use display
// [REQ:BSI-eRp-ePA:O.Arch_8#1] Webview containint local html without javascript
struct TermsOfUseView: View {
../Sources/eRpApp/Screens/Settings/FOSS/FOSSView.swift:10
Webview containint local html without javascript
// [REQ:BSI-eRp-ePA:O.Arch_8#2] Webview containint local html without javascript
struct FOSSView: View {
../Sources/eRpApp/Screens/Settings/DataPrivacy/DataPrivacyView.swift:13
Webview containing local html without javascript
// [REQ:BSI-eRp-ePA:O.Purp_1#2] Actual View driving the display of `DataPrivacy.html`
// [REQ:BSI-eRp-ePA:O.Arch_8#3] Webview containing local html without javascript
// [REQ:gemSpec_eRp_FdV:A_19980#2] Actual View driving the display of `DataPrivacy.html`
struct DataPrivacyView: View {
O.Arch_9
Link within DataPrivacy.html to https://www.gematik.de/datensicherheit
../Sources/eRpApp/Screens/Onboarding/OnboardingLegalInfoView.swift:162
DataPrivacy display within Onboarding
// [REQ:BSI-eRp-ePA:O.Arch_9#2] DataPrivacy display within Onboarding
.sheet(isPresented: $showTermsOfPrivacy) {
../Sources/eRpApp/Screens/Settings/SettingsLegalInfoView.swift:44
DataPrivacy display within Settings
// [REQ:BSI-eRp-ePA:O.Arch_9#3] DataPrivacy display within Settings
NavigationLinkStore(
O.Arch_10
Currently only implemented via APIKey usage against APOVZD and FD. All requests against the backends may respond with an 403 status code. The App stays usable, but only without any server connection.
O.Arch_11
Not implemented
O.Arch_12
Not necessary due to O.Arch_11 not being implemented.
O.Auth_1
Our authentication concept is described in the following repository: https://github.com/gematik/api-erp/blob/master/docs/authentisieren.adoc ||| We have more detailed diagrams regarding the context of the authentication in the SIS: Authentication on the central IDP: E-Rezept-App-Authentifizierungskonzept.pdf ||| Authentication via Fast Track: E-Rezept-App-Authentifizierungskonzept_Fast_Track.pdf
O.Auth_2
There is no client side separation of authentication and authorization.
O.Auth_3
One way to connect to the FD is to login by using the eGK. To login with the eGK, the card, a CAN and a PIN is needed. The other way is to use a insurance company provided app. These apps must implement a second factor as well. See all CardWall
prefixed files for all implementation details.
../Sources/eRpApp/Screens/CardWall/ExtAuth/CardWallExtAuthConfirmationDomain.swift:82
Start login with KK
// [REQ:gemSpec_IDP_Sek:A_22294] Start login with KK
// [REQ:BSI-eRp-ePA:O.Auth_3#2,O.Plat_10#2] Start login with KK
return .publisher(
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.swift:184
Implementation of eGK connection
// [REQ:BSI-eRp-ePA:O.Auth_3#2] Implementation of eGK connection
case let .signChallenge(challenge):
O.Auth_4
There is no step up from always using 2nd factor authentication. Tokens have short lifetimes of 12h (server defined) SSO-Tokens and 5m Access-Tokens.
O.Auth_5
A Audit Log for every FD access is available in each user profile.
../Sources/eRpApp/Screens/Settings/Profiles/Edit/EditProfileView.swift:516
Actual Button to open the audit events
// [REQ:gemSpec_eRp_FdV:A_19177#2,A_19185#3] Actual Button to open the audit events
// [REQ:BSI-eRp-ePA:O.Auth_5#2] Actual Button to open the audit events
NavigationLinkStore(
../Sources/eRpApp/Screens/Settings/Profiles/Protocol/AuditEventsView.swift:11
View displaying the audit events
// [REQ:gemSpec_eRp_FdV:A_19177#1,A_19185#2] View displaying the audit events
// [REQ:BSI-eRp-ePA:O.Auth_5#3] View displaying the audit events
struct AuditEventsView: View {
O.Auth_6
There are not passwords to guess for server login within our application. Only eGK login is directly available within the application. The users application password is not delayed, as any user with PIN access would have access to the unencrypted file system as well.
../Sources/eRpApp/Screens/AppAuthentication/AppAuthenticationPassword/AppAuthenticationPasswordDomain.swift:8
Domain handling App Authentication
// [REQ:BSI-eRp-ePA:O.Auth_6#2] Domain handling App Authentication
struct AppAuthenticationPasswordDomain: ReducerProtocol {
../Sources/eRpApp/Session/Helper/AppSecurityManager.swift:41
Actual password check
// [REQ:BSI-eRp-ePA:O.Auth_6#3] Actual password check
func matches(password: String) throws -> Bool {
O.Auth_7
The SceneDelegate exchanges the active window with an authentication window every time the app gains focus
../Sources/eRpApp/SceneDelegate.swift:164
Present the authentication window
// [REQ:BSI-eRp-ePA:O.Auth_7#2] Present the authentication window
// dispatching necessary to prevents keyboard not showing on iOS 16
DispatchQueue.main.async { [weak self] in
O.Auth_8
A Timer is used to measure the time a user is inactive. Every user interaction resets the timer.
../Sources/eRpApp/SceneDelegate.swift:312
A timer is used to determine inactive users
// [REQ:BSI-eRp-ePA:O.Auth_8#2] A timer is used to determine inactive users
self?.presentAppAuthenticationDomain(scene: scene)
../Sources/eRpApp/SceneDelegate.swift:293
the timer is reset on user interaction
// [REQ:BSI-eRp-ePA:O.Auth_8#3] the timer is reset on user interaction
self?.setupTimer(scene: scene)
../Sources/eRpApp/SceneDelegate.swift:322
User interaction is determined by using a higher order reducer watching all
// [REQ:BSI-eRp-ePA:O.Auth_8#4] User interaction is determined by using a higher order reducer watching all
// actions
NotificationCenter.default.post(name: .userInteractionDetected, object: nil, userInfo: nil)
../Sources/eRpApp/SceneDelegate.swift:45
Concat the user interaction reducer to the normal application reducer
// [REQ:BSI-eRp-ePA:O.Auth_8#5] Concat the user interaction reducer to the normal application reducer
reducer: AppStartDomain().analytics().notifyUserInteraction().prepareUITestsDependencies(),
O.Auth_9
Token invalidation happens after 12 hours. If a user is still active, a re-authentication via eGK, Biometrics or Insurance App is necessary. Each meaning the possession and or knowledge of the needed user input.
../Sources/IDP/DefaultIDPSession.swift:801
application is also checking for expiration
// [REQ:BSI-eRp-ePA:O.Auth_9#2] application is also checking for expiration
guard token.expires > time() else {
O.Auth_10
Authentication via eGK cannot be altered, as the physical card cannot be modified without authentication (e.g. PIN change). See gemSpec_COS for details. Adding a authentication key that is secured via biometrics (labeled as “save login” within the card wall) enforces a new authentication via eGK on server side.
O.Auth_11
We use TLS Pinning and a Trust Store for VAU communication. See TrustStore and VAU Module for implementation.
O.Auth_12
unused
O.Biom_1
The authentication via biometric is only available upon successfull authentication via eGK + PIN.
O.Biom_2
Minimal OS version is set to iOS 15 in compliance with TR-03161-1 Anhang C. See options.deploymentTarget.iOS
path within project.yml
or the generated IPHONEOS_DEPLOYMENT_TARGET
within the project.pbxproj
file.
O.Biom_3
Minimal OS version is set to iOS 15 in compliance with TR-03161-1 Anhang C. See options.deploymentTarget.iOS
path within project.yml
or the generated IPHONEOS_DEPLOYMENT_TARGET
within the project.pbxproj
file.
We do not access the sensor directly. All access is hidden behind OS Frameworks. A Denylist is implemented by the IDP while registering the device for biometric access.
O.Biom_4
Minimal OS version is set to iOS 15 in compliance with TR-03161-1 Anhang C. See options.deploymentTarget.iOS
path within project.yml
or the generated IPHONEOS_DEPLOYMENT_TARGET
within the project.pbxproj
file.
O.Biom_5
../Sources/eRpApp/Screens/AppAuthentication/AppAuthenticationDomain.swift:129
Check whether at least one biometric reference is available.
// [REQ:BSI-eRp-ePA:O.Biom_5#1] Check whether at least one biometric reference is available.
func loadAppAuthenticationOption() -> EffectTask<AppAuthenticationDomain.Action> {
O.Biom_6
Biometric secured private keys are invalid whenever the biometrics setup changes.
../Sources/IDP/PrivateKeyContainer.swift:132
invalidates biometry after changes
// [REQ:gemSpec_IDP_Frontend:A_21586] invalidates biometry after changes
// [REQ:BSI-eRp-ePA:O.Biom_6#2] invalidates biometry after changes
.biometryCurrentSet], &error) else {
O.Biom_7
../Sources/eRpApp/Screens/AppAuthentication/AppAuthenticationBiometry/BiometricsAuthenticationChallengeProvider.swift:78
Live implementation of AuthenticationChallengeProvider for FaceID methods
// [REQ:BSI-eRp-ePA:O.Biom_7#1] Live implementation of AuthenticationChallengeProvider for FaceID methods
struct BiometricsAuthenticationChallengeProvider: AuthenticationChallengeProvider {
../Sources/eRpApp/Session/Helper/SystemKeychainAccessHelper.swift:206
Live implementation of KeychainAccessHelper that reaches into system’s Keychain methods
// [REQ:BSI-eRp-ePA:O.Biom_7#2] Live implementation of KeychainAccessHelper that reaches into system's Keychain methods
struct SystemKeychainAccessHelper: KeychainAccessHelper {
O.Biom_8
If evaluation failed 5 times, a not escapable error is presented
../Sources/eRpApp/Screens/AppAuthentication/AppAuthenticationBiometry/BiometricsAuthenticationChallengeProvider.swift:32
see also:
// [REQ:BSI-eRp-ePA:O.Biom_8#2] see also:
// https://developer.apple.com/documentation/localauthentication/laerror/code/2867589-biometrylockout
if LAError.Code(rawValue: error.code) == LAError.biometryLockout {
O.Cryp_1
Private keys for encryption are either created and stored within the secure enclave or stored within the eGK. As encryption for Server communication is done ephemeral via ECDH-ES, no static private keys on client side are necessary.
../Sources/IDP/Models/SignedChallenge.swift:36
Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_1#2] Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_4#5] one time usage for JWE ECDH-ES Encryption
let algorithm = JWE.Algorithm.ecdh_es(JWE.Algorithm.KeyExchangeContext.bpp256r1(
../Sources/IDP/Models/SignedAuthenticationData.swift:27
Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_1#3] Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_4#4] one time usage for JWE ECDH-ES Encryption
let algorithm = JWE.Algorithm.ecdh_es(JWE.Algorithm.KeyExchangeContext.bpp256r1(
../Sources/IDP/Models/RegistrationData.swift:108
Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_1#4] Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_4#3] one time usage for JWE ECDH-ES Encryption
let algorithm = JWE.Algorithm.ecdh_es(JWE.Algorithm.KeyExchangeContext.bpp256r1(
../Sources/IDP/internal/JWT/JWE+Encryption.swift:20
Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_1#5] Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_4#6] one time usage for JWE ECDH-ES Encryption
let algorithm = JWE.Algorithm.ecdh_es(JWE.Algorithm.KeyExchangeContext.bpp256r1(
../Sources/IDP/internal/TokenPayload.swift:180
Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_1#6] Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_4#2] one time usage for JWE ECDH-ES Encryption
guard let jweHeader = try? JWE.Header(algorithm: JWE.Algorithm.ecdh_es(keyExchangeContext),
O.Cryp_2
All cryptographic requirements are defined within gemSpec_Krypt
. The document was created together with BSI.
O.Cryp_3
All cryptographic requirements are defined within gemSpec_Krypt
. The document was created together with BSI.
../Sources/IDP/PrivateKeyContainer.swift:62
Secure Enclave Key generation
// Keychain Query
// [REQ:BSI-eRp-ePA:O.Cryp_3#2,O.Cryp_6#2] Secure Enclave Key generation
let query: [String: Any] = [kSecClass as String: kSecClassKey,
../Sources/IDP/internal/IDPCrypto.swift:45
Brainpool key generator
// [REQ:gemSpec_eRp_FdV:A_19179#2] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:BSI-eRp-ePA:O.Cryp_3#3] Brainpool key generator
try BrainpoolP256r1.KeyExchange.generateKey()
../Sources/IDP/internal/JWT/JWE+KDF.swift:35
Brainpool key generator
// [REQ:BSI-eRp-ePA:O.Cryp_3#4] Brainpool key generator
// [REQ:gemSpec_eRp_FdV:A_19179#4] Key pair generation delegated to OpenSSL
= { try BrainpoolP256r1.KeyExchange.generateKey() })
../Sources/VAUClient/internal/VAUCrypto.swift:99
Brainpool key generator
// [REQ:gemSpec_Krypt:GS-A_4357] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4367] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:BSI-eRp-ePA:O.Cryp_3#5] Brainpool key generator
// [REQ:gemSpec_eRp_FdV:A_19179#5] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
let keyPairGenerator = { try BrainpoolP256r1.KeyExchange.generateKey() }
O.Cryp_4
All cryptographic requirements are defined within gemSpec_Krypt
. The document was created together with BSI.
../Sources/IDP/internal/TokenPayload.swift:181
one time usage for JWE ECDH-ES Encryption
// [REQ:BSI-eRp-ePA:O.Cryp_1#6] Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_4#2] one time usage for JWE ECDH-ES Encryption
guard let jweHeader = try? JWE.Header(algorithm: JWE.Algorithm.ecdh_es(keyExchangeContext),
../Sources/IDP/Models/RegistrationData.swift:109
one time usage for JWE ECDH-ES Encryption
// [REQ:BSI-eRp-ePA:O.Cryp_1#4] Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_4#3] one time usage for JWE ECDH-ES Encryption
let algorithm = JWE.Algorithm.ecdh_es(JWE.Algorithm.KeyExchangeContext.bpp256r1(
../Sources/IDP/Models/SignedAuthenticationData.swift:28
one time usage for JWE ECDH-ES Encryption
// [REQ:BSI-eRp-ePA:O.Cryp_1#3] Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_4#4] one time usage for JWE ECDH-ES Encryption
let algorithm = JWE.Algorithm.ecdh_es(JWE.Algorithm.KeyExchangeContext.bpp256r1(
../Sources/IDP/Models/SignedChallenge.swift:37
one time usage for JWE ECDH-ES Encryption
// [REQ:BSI-eRp-ePA:O.Cryp_1#2] Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_4#5] one time usage for JWE ECDH-ES Encryption
let algorithm = JWE.Algorithm.ecdh_es(JWE.Algorithm.KeyExchangeContext.bpp256r1(
../Sources/IDP/internal/JWT/JWE+Encryption.swift:21
one time usage for JWE ECDH-ES Encryption
// [REQ:BSI-eRp-ePA:O.Cryp_1#5] Signature via ecdh ephemeral-static
// [REQ:BSI-eRp-ePA:O.Cryp_4#6] one time usage for JWE ECDH-ES Encryption
let algorithm = JWE.Algorithm.ecdh_es(JWE.Algorithm.KeyExchangeContext.bpp256r1(
O.Cryp_5
We use Brainpool256R1 and ECSECPrimeRandom 256, see usages in O.Cryp_1 to O.Cryp_4
O.Cryp_6
Persisted cryptographic keys are created within the devices secure enclave. Temporal keys are discarded as soon as usage is no longer needed.
../Sources/IDP/PrivateKeyContainer.swift:62
Secure Enclave Key generation
// Keychain Query
// [REQ:BSI-eRp-ePA:O.Cryp_3#2,O.Cryp_6#2] Secure Enclave Key generation
let query: [String: Any] = [kSecClass as String: kSecClassKey,
O.Cryp_7
As Brainpool256R1 is not available within secure enclave but enforced by BSI where possible, we use secure enclave encryption only for biometric authentication. Everywhere else, cryptographic operations are ephemeral or use the eGK as a secure execution environment.
../Sources/IDP/PrivateKeyContainer.swift:20
Container for private key operations using secure enclave private keys
/// Represents a (SecureEnclave) private key, namely `PrK_SE_AUT`, secured by iOS Biometrics.
///
/// [REQ:gemSpec_IDP_Frontend:A_21590] This is the container to represent biometric keys. Usage is limited to
/// authorization purposes
/// [REQ:BSI-eRp-ePA:O.Cryp_7#2] Container for private key operations using secure enclave private keys
public struct PrivateKeyContainer {
O.Data_1
User preferences are asked without discrimination within the onboarding process
O.Data_2
We use the OS Keychain to persist sensitive data. The keychain either stores or encrypts via SecureEnclave.
../Sources/eRpApp/Session/KeychainStorage.swift:23
Implementation of data storage that is
// [REQ:gemSpec_eRp_FdV:A_19186]
// [REQ:gemSpec_eRp_FdV:A_19188] Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_IDP_Frontend:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemF_Tokenverschlüsselung:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Implementation
// [REQ:BSI-eRp-ePA:O.Purp_8#1,O.Arch_2#5,O.Arch_4#3,O.Source_7#2,O.Data_2#2] Implementation of data storage that is
// persisted via keychain
class KeychainStorage: SecureUserDataStore, IDPStorage, SecureEGKCertificateStorage {
O.Data_3
Currently it is not possible to store data within the secure enclave on iOS. We store all data on the encrypted application container on the device. See eRpApp.entitlements
for NSFileProtectionCompleteUnlessOpen
usage.
O.Data_4
Everything we store, safe or send anywhere is both verified as in O.Resi_4 and or encrypted, such as Keychain or the application container.
O.Data_5
Ephemeral private keys are released as soon as they are no longer needed (see O.Source_5)
O.Data_6
Collected data is sparse and use case related as required.
../Sources/eRpApp/Screens/CardWall/CAN/CardWallCANView.swift:162
CAN is used for eGK connection
// [REQ:BSI-eRp-ePA:O.Purp_2#2,O.Data_6#2] CAN is used for eGK connection
CardWallCANInputView(
../Sources/eRpApp/Screens/CameraView/ErxTaskScannerView.swift:27
Scanning tasks contains purpose related data input
// [REQ:BSI-eRp-ePA:O.Purp_2#1,O.Data_6#3] Scanning tasks contains purpose related data input
// [REQ:BSI-eRp-ePA:O.Source_1#1] Scanning tasks starts with scanner callback
viewStore.send(.analyse(scanOutput: $0))
../Sources/eRpApp/Screens/CardWall/PIN/CardWallPINView.swift:30
PIN is used for eGK Connection
// [REQ:BSI-eRp-ePA:O.Purp_2#3,O.Data_6#4] PIN is used for eGK Connection
PINView(store: store).padding()
../Sources/eRpApp/Screens/Pharmacy/Redeem/PharmacyContactView.swift:10
Contact information is collected when needed for redeeming
// [REQ:BSI-eRp-ePA:O.Purp_2#6,O.Data_6#5] Contact information is collected when needed for redeeming
struct PharmacyContactView: View {
../Sources/eRpApp/Tracking/AnalyticsReducer.swift:16
User interaction analytics trigger …
// [REQ:BSI-eRp-ePA:O.Purp_2#4,O.Purp_4#1,O.Data_6#6] User interaction analytics trigger ...
struct AnalyticsReducer<ContentReducer: ReducerProtocol>: ReducerProtocol
../Sources/eRpApp/Tracking/ContentSquareAnalyticsAdapter.swift:99
… records data only if user opt-in is given.
// [REQ:BSI-eRp-ePA:O.Purp_2#5,O.Purp_4#2,O.Data_6#7] ... records data only if user opt-in is given.
if optIn {
O.Data_7
Private data is not initially created by the application. Only additional information, such as redeeming or deleting prescriptions create data. The created data is kept on the FD.
O.Data_8
The device camera is used for scanning prescriptions.
../Sources/eRpApp/Screens/CameraView/AVScannerView.swift:40
This controller uses the camera as an input device. Frames are processed but never
// [REQ:BSI-eRp-ePA:O.Data_8#2] This controller uses the camera as an input device. Frames are processed but never
// stored, metadata is never created here.
class AVScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
O.Data_9
The device camera is used for scanning prescriptions, no picture files are created.
O.Data_10
Password fields are marked as such and thus disallow autocorrections. Search for SecureField
or SecureFieldWithReveal
for all usages.
../Sources/eRpApp/AppDelegate.swift:36
Keyboard extensions are disallowed
// [REQ:BSI-eRp-ePA:O.Data_10#2] Keyboard extensions are disallowed
func application(
../Sources/eRpApp/Components/Controls/SecureFieldWithReveal.swift:35
SecureFields
are used for password input.
// [REQ:BSI-eRp-ePA:O.Data_10#3] `SecureFields` are used for password input.
SecureField(titleKey, text: $text, onCommit: onCommit)
O.Data_11
We use default platform behavior for password fields.
O.Data_12
There is no API from Apple that allows extraction of biometric data.
O.Data_13
Suppressing Screenshots is not possible as of now.
../Sources/eRpApp/SceneDelegate.swift:228
Moving the application to the background blurs the application
// [REQ:BSI-eRp-ePA:O.Data_13#2,O.Plat_12#2] Moving the application to the background blurs the application
// window.
addBlurOverlayToWindow()
../Sources/eRpApp/SceneDelegate.swift:158
Moving the application to the foreground removes the blur.
// [REQ:BSI-eRp-ePA:O.Data_13#3,O.Plat_12#3] Moving the application to the foreground removes the blur.
removeBlurOverlayFromWindow()
O.Data_14
See eRpApp.entitlements
for NSFileProtectionCompleteUnlessOpen
usage.
O.Data_15
Biometric keys are always bound to the device and cannot leave the secure enclave. Actual user data is excluded from backups and cannot be exported.
../Sources/eRpLocalStorage/CoreDataController.swift:84
Database backup exclusion
// [REQ:BSI-eRp-ePA:O.Purp_8#4,O.Arch_2#4,O.Arch_4#2,O.Data_15#1] Database backup exclusion
if excludeFromBackup, let storeUrl = store.url {
../Sources/IDP/PrivateKeyContainer.swift:126
prevents migration to other devices
// [REQ:gemSpec_IDP_Frontend:A_21586] prevents migration to other devices
// [REQ:BSI-eRp-ePA:O.Data_15#2] prevents migration to other devices
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
O.Data_16
Storing application data is not possible on iOS.
O.Data_17
Deletion is completely handled by the OS. We have no means of doing anything while deinstallation is running. All sensitive keychain data is tied to the user profile id and cannot be accessed after deinstallation. though technically the information still persists for a short period of time (<24h) after some kind of daily cleanup routine by the OS it will be removed. The user may choose to manually logout or delete profiles before app deletion.
O.Data_18
Deletion is completely handled by the OS. We have no means of doing anything while deinstallation is running. All sensitive keychain data is tied to the user profile id and cannot be accessed after deinstallation. though technically the information still persists for a short period of time (<24h) after some kind of daily cleanup routine by the OS it will be removed. The user may choose to manually logout or delete profiles before app deletion.
O.Data_19
Not applicable: no kill switch realized.
O.Ntwk_1
no exceptions set in NSAppTransportSecurity, HTTP via TLS is enforced; OS will use system root certificates in combination with set pinned certificates. see also: Requirements for Connecting Using ATS
O.Ntwk_2
We use one wrapper around NSURLSession for network communication that uses an ephemeral configuration only allowing TLS 1.2 or greater.
../Sources/HTTPClient/DefaultHTTPClient.swift:42
URLSession is used as Network Framework
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
O.Ntwk_3
We use NSURLSession for network communication
../Sources/HTTPClient/DefaultHTTPClient.swift:42
URLSession is used as Network Framework
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
O.Ntwk_4
The App Transport Security Settings for all pinned certificates and the supported domains is done in eRpApp/Resources/Info.plist
. For more informations take a look at the apple documentation of How to configure server certificates and NSPinnedDomains.
O.Ntwk_5
By using NSURLSession ATS (App Transport Security) handles that within the application.
O.Ntwk_6
The server uses extended validation certificates to ensure maximum authenticity.
O.Ntwk_7
The system enforces ATS App Transport Security since the app uses the standard URL Loading System URLSession
which automatically negotiate the most secure connection available from the server. Also there are no exceptions configured in the eRpApp/Resources/Info.plist
thus insecure networt connections are disabled by default.
../Sources/HTTPClient/DefaultHTTPClient.swift:42
URLSession is used as Network Framework
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
O.Ntwk_8
Since this is a requirement for a backend system, it is not applicable to the FdV
O.Ntwk_9
Since this is a requirement for a backend system, it is not applicable to the FdV
O.Paid_1
The app does not offer any purchases.
O.Paid_2
The app does not offer any purchases.
O.Paid_3
The app does not offer any purchases.
O.Paid_4
The app does not offer any purchases.
O.Paid_5
The app does not offer any purchases.
O.Paid_6
The app does not offer any purchases.
O.Paid_7
The app does not offer any purchases.
O.Paid_8
The app does not offer any purchases.
O.Paid_9
The app does not offer any purchases.
O.Paid_10
The app does not offer any purchases.
O.Pass_1
We use zxcvbn-ios as a password strength indicator and enforcer for the application password.
O.Pass_2
We use zxcvbn-ios as a password strength indicator and enforcer for the application password.
O.Pass_3
The user may change the app passwords within the settings.
../Sources/eRpApp/Screens/Settings/AppSecurity/CreatePasswordView.swift:10
View for changing the user password
// [REQ:BSI-eRp-ePA:O.Pass_3#2] View for changing the user password
struct CreatePasswordView: View {
O.Pass_4
There is no protocol for the application password within the application. If there is one, the keychain may hold it.
O.Pass_5
We use the keychain to persist the app password. The app password is not hashed.
O.Plat_1
We test for device pincode at startup and show a dialog if no pin is set.
../Sources/eRpApp/Screens/Main/MainView.swift:120
trigger device security check
// [REQ:BSI-eRp-ePA:O.Arch_6#2,O.Resi_2#2,O.Plat_1#2] trigger device security check
viewStore.send(.loadDeviceSecurityView)
../Sources/eRpApp/Session/Helper/DeviceSecurityManager.swift:58
calculate system risk for jailbreak and missing device pin
// [REQ:BSI-eRp-ePA:O.Arch_6#3,O.Resi_2#3,O.Plat_1#3] calculate system risk for jailbreak and missing device pin
var showSystemSecurityWarning: AnyPublisher<DeviceSecurityWarningType, Never> {
../Sources/eRpApp/Session/Helper/DeviceSecurityManager.swift:68
Missing system pin detection
// [REQ:BSI-eRp-ePA:O.Plat_1#4] Missing system pin detection
var informMissingSystemPin: AnyPublisher<Bool, Never> {
O.Plat_2
The app ony configures entitlements that are used for it’s primary purpose. All configured entitlements are configured in eRpApp/Resources/Info.plist
and in eRpApp/Resources/eRpApp.entitlements
.
O.Plat_3
We use the platform dialogs for this. Localizations can be found within InfoPlist.strings
O.Plat_4
We never show sensitive data as all errors are localized with custom descriptions (See LocalizedError
implementations). Some errors have localized parameters such as a failed PIN counter. Some errors are sent from the server, these messages show only generic information what went wrong.
O.Plat_5
Not applicable: displaying of messages not realized.
O.Plat_6
Implemented by the OS Sandboxing.
O.Plat_7
Implemented by the OS Sandboxing.
O.Plat_8
Not applicable: broadcasting of messages not realized. Sandboxing does not allow that Broadcasts from NSNotificationCenter leave the application.
O.Plat_9
Not applicable: broadcasting of messages not realized.
O.Plat_10
Interprocess communication is implemented using Universal Linking. Current use cases are limited to login with insurance company apps. No sensitive data is transferred, the actual payload is decided by the server.
../Sources/eRpApp/Screens/CardWall/ExtAuth/CardWallExtAuthConfirmationDomain.swift:82
Start login with KK
// [REQ:gemSpec_IDP_Sek:A_22294] Start login with KK
// [REQ:BSI-eRp-ePA:O.Auth_3#2,O.Plat_10#2] Start login with KK
return .publisher(
../Sources/eRpApp/Screens/CardWall/ExtAuth/CardWallExtAuthConfirmationDomain.swift:97
Follow redirect
// [REQ:gemSpec_IDP_Sek:A_22299] Follow redirect
// [REQ:BSI-eRp-ePA:O.Plat_10#3] Follow redirect
guard environment.resourceHandler.canOpenURL(url) else {
O.Plat_11
WebViews only display local content that is delivered together with the application. Javascript is disabled, linking and loading other content is disabled. All website open the system browser.
../Sources/eRpApp/Screens/Settings/DataPrivacy/DataPrivacyView.swift:27
disabled javascript
// [REQ:BSI-eRp-ePA:O.Plat_11#2] disabled javascript
wkWebView.configuration.defaultWebpagePreferences.allowsContentJavaScript = false
../Sources/eRpApp/Screens/Settings/TermsOfUse/TermsOfUseView.swift:26
disabled javascript
// [REQ:BSI-eRp-ePA:O.Plat_11#3] disabled javascript
wkWebView.configuration.defaultWebpagePreferences.allowsContentJavaScript = false
../Sources/eRpApp/Screens/Settings/FOSS/FOSSView.swift:25
disabled javascript
// [REQ:BSI-eRp-ePA:O.Plat_11#4] disabled javascript
wkWebView.configuration.defaultWebpagePreferences.allowsContentJavaScript = false
O.Plat_12
The content window is blurred upon leaving the application to not expose content to the system multitasking switcher.
../Sources/eRpApp/SceneDelegate.swift:228
Moving the application to the background blurs the application
// [REQ:BSI-eRp-ePA:O.Data_13#2,O.Plat_12#2] Moving the application to the background blurs the application
// window.
addBlurOverlayToWindow()
../Sources/eRpApp/SceneDelegate.swift:158
Moving the application to the foreground removes the blur.
// [REQ:BSI-eRp-ePA:O.Data_13#3,O.Plat_12#3] Moving the application to the foreground removes the blur.
removeBlurOverlayFromWindow()
O.Plat_13
Implemented using a WKWebViewDelegate
.
../Sources/eRpApp/Screens/Settings/DataPrivacyTermsOfUseNavigationDelegate.swift:9
Delegate disables unused schemes
// [REQ:BSI-eRp-ePA:O.Plat_13#2] Delegate disables unused schemes
class DataPrivacyTermsOfUseNavigationDelegate: NSObject, WKNavigationDelegate {
../Sources/eRpApp/Screens/Settings/DataPrivacy/DataPrivacyView.swift:23
Usage of the delegate
// swiftlint:disable:next weak_delegate
// [REQ:BSI-eRp-ePA:O.Plat_13#3] Usage of the delegate
let navigationDelegate = DataPrivacyTermsOfUseNavigationDelegate()
../Sources/eRpApp/Screens/Settings/FOSS/FOSSView.swift:21
Usage of the delegate
// swiftlint:disable:next weak_delegate
// [REQ:BSI-eRp-ePA:O.Plat_13#4] Usage of the delegate
let navigationDelegate = DataPrivacyTermsOfUseNavigationDelegate()
../Sources/eRpApp/Screens/Settings/TermsOfUse/TermsOfUseView.swift:22
Usage of the delegate
// swiftlint:disable:next weak_delegate
// [REQ:BSI-eRp-ePA:O.Plat_13#5] Usage of the delegate
let navigationDelegate = DataPrivacyTermsOfUseNavigationDelegate()
O.Plat_14
WebViews only display local content that is delivered together with the application. No cookies are created.
O.Plat_15
The app’s memory is subject to the operating system’s memory and therefore it has no control over the used memory.
O.Plat_16
During the onboarding process the user is forced to secure the access to the app either via a biometric trait or a strong password.
O.Purp_1
../Sources/eRpApp/Screens/Onboarding/OnboardingLegalInfoView.swift:141
Display as part of the onboarding
// [REQ:BSI-eRp-ePA:O.Purp_1#1] Display as part of the onboarding
struct OnboardingPrivacyView: View {
../Sources/eRpApp/Screens/Settings/DataPrivacy/DataPrivacyView.swift:12
Actual View driving the display of DataPrivacy.html
// [REQ:BSI-eRp-ePA:O.Purp_1#2] Actual View driving the display of `DataPrivacy.html`
// [REQ:BSI-eRp-ePA:O.Arch_8#3] Webview containing local html without javascript
// [REQ:gemSpec_eRp_FdV:A_19980#2] Actual View driving the display of `DataPrivacy.html`
struct DataPrivacyView: View {
O.Purp_2
../Sources/eRpApp/Screens/CameraView/ErxTaskScannerView.swift:27
Scanning tasks contains purpose related data input
// [REQ:BSI-eRp-ePA:O.Purp_2#1,O.Data_6#3] Scanning tasks contains purpose related data input
// [REQ:BSI-eRp-ePA:O.Source_1#1] Scanning tasks starts with scanner callback
viewStore.send(.analyse(scanOutput: $0))
../Sources/eRpApp/Screens/CardWall/CAN/CardWallCANView.swift:162
CAN is used for eGK connection
// [REQ:BSI-eRp-ePA:O.Purp_2#2,O.Data_6#2] CAN is used for eGK connection
CardWallCANInputView(
../Sources/eRpApp/Screens/CardWall/PIN/CardWallPINView.swift:30
PIN is used for eGK Connection
// [REQ:BSI-eRp-ePA:O.Purp_2#3,O.Data_6#4] PIN is used for eGK Connection
PINView(store: store).padding()
../Sources/eRpApp/Tracking/AnalyticsReducer.swift:16
User interaction analytics trigger …
// [REQ:BSI-eRp-ePA:O.Purp_2#4,O.Purp_4#1,O.Data_6#6] User interaction analytics trigger ...
struct AnalyticsReducer<ContentReducer: ReducerProtocol>: ReducerProtocol
../Sources/eRpApp/Tracking/ContentSquareAnalyticsAdapter.swift:99
… records data only if user opt-in is given.
// [REQ:BSI-eRp-ePA:O.Purp_2#5,O.Purp_4#2,O.Data_6#7] ... records data only if user opt-in is given.
if optIn {
../Sources/eRpApp/Screens/Pharmacy/Redeem/PharmacyContactView.swift:10
Contact information is collected when needed for redeeming
// [REQ:BSI-eRp-ePA:O.Purp_2#6,O.Data_6#5] Contact information is collected when needed for redeeming
struct PharmacyContactView: View {
O.Purp_3
../Sources/eRpApp/Screens/Onboarding/OnboardingLegalInfoView.swift:103
Terms of Use display is part of the onboarding
// [REQ:BSI-eRp-ePA:O.Purp_3#1] Terms of Use display is part of the onboarding
struct OnboardingTermsOfUseView: View {
../Sources/eRpApp/Screens/Settings/TermsOfUse/TermsOfUseView.swift:11
Actual view for the Terms of Use display
// [REQ:BSI-eRp-ePA:O.Purp_3#2] Actual view for the Terms of Use display
// [REQ:BSI-eRp-ePA:O.Arch_8#1] Webview containint local html without javascript
struct TermsOfUseView: View {
../Sources/eRpApp/Screens/Onboarding/OnboardingLegalInfoView.swift:41
User acceptance
// [REQ:BSI-eRp-ePA:O.Purp_3#3] User acceptance
OnboardingLegalInfoCheckmarkView(isAccepted: $isAllAccepted)
../Sources/eRpApp/Screens/Onboarding/OnboardingContainer.swift:62
Callback triggers tracking alert
// [REQ:BSI-eRp-ePA:O.Purp_3#4] Callback triggers tracking alert
viewStore.send(.showTracking)
../Sources/eRpApp/Screens/Onboarding/OnboardingDomain.swift:190
Show comply route to display analytics usage within onboarding
// [REQ:gemSpec_eRp_FdV:A_19088,A_19091#1,A_19092] Show comply route to display analytics usage within
// onboarding
// [REQ:BSI-eRp-ePA:O.Purp_3#5] Show comply route to display analytics usage within onboarding
case .showTracking:
../Sources/eRpApp/Screens/Onboarding/OnboardingDomain.swift:194
Accept usage analytics
// [REQ:gemSpec_eRp_FdV:A_19090,A_19091#2] User confirms the opt in within settings
// [REQ:BSI-eRp-ePA:O.Purp_3#6] Accept usage analytics
case .alert(.presented(.allowTracking)):
../Sources/eRpApp/Screens/Onboarding/OnboardingDomain.swift:199
Deny usage analytics
// [REQ:gemSpec_eRp_FdV:A_19092] User may choose to not accept analytics
// [REQ:BSI-eRp-ePA:O.Purp_3#7] Deny usage analytics
case .alert(.dismiss):
O.Purp_4
../Sources/eRpApp/Tracking/AnalyticsReducer.swift:16
User interaction analytics trigger …
// [REQ:BSI-eRp-ePA:O.Purp_2#4,O.Purp_4#1,O.Data_6#6] User interaction analytics trigger ...
struct AnalyticsReducer<ContentReducer: ReducerProtocol>: ReducerProtocol
../Sources/eRpApp/Tracking/ContentSquareAnalyticsAdapter.swift:99
… records data only if user opt-in is given.
// [REQ:BSI-eRp-ePA:O.Purp_2#5,O.Purp_4#2,O.Data_6#7] ... records data only if user opt-in is given.
if optIn {
O.Purp_5
../Sources/eRpApp/Screens/Settings/SettingsView.swift:236
Toggle within Settings to enable and disable usage tracking
// [REQ:gemSpec_eRp_FdV:A_19097] Toggle within Settings to enable and disable usage tracking
// [REQ:BSI-eRp-ePA:O.Purp_5#1] Toggle within Settings to enable and disable usage tracking
// [REQ:BSI-eRp-ePA:O.Purp_6#1] Current Analytics state is inspectable by the user
// [REQ:gemSpec_eRp_FdV:A_19982#4] Opt out of analytics
Toggle(isOn: viewStore.binding(
../Sources/eRpApp/Screens/Settings/SettingsDomain.swift:199
Actual disabling of analytics
// Tracking
// [REQ:gemSpec_eRp_FdV:A_19088, A_19089, A_19092, A_19097] OptIn for usage tracking
// [REQ:BSI-eRp-ePA:O.Purp_5#2] Actual disabling of analytics
// [REQ:gemSpec_eRp_FdV:A_19982#2] Opt out of analytics
case let .toggleTrackingTapped(optIn):
../Sources/eRpApp/Screens/Settings/SettingsView.swift:61
Show comply view for settings triggered analytics enabling
// Tracking comply sheet presentation
// [REQ:BSI-eRp-ePA:O.Purp_5#3] Show comply view for settings triggered analytics enabling
// [REQ:gemSpec_eRp_FdV:A_19982#3] Opt out of analytics
Rectangle()
../Sources/eRpApp/Screens/Settings/SettingsDomain.swift:210
User confirms the opt in within settings
// [REQ:gemSpec_eRp_FdV:A_19090,A_19091#4] User confirms the opt in within settings
// [REQ:BSI-eRp-ePA:O.Purp_5#4] User confirms the opt in within settings
case .confirmedOptInTracking:
O.Purp_6
../Sources/eRpApp/Screens/Settings/SettingsView.swift:237
Current Analytics state is inspectable by the user
// [REQ:gemSpec_eRp_FdV:A_19097] Toggle within Settings to enable and disable usage tracking
// [REQ:BSI-eRp-ePA:O.Purp_5#1] Toggle within Settings to enable and disable usage tracking
// [REQ:BSI-eRp-ePA:O.Purp_6#1] Current Analytics state is inspectable by the user
// [REQ:gemSpec_eRp_FdV:A_19982#4] Opt out of analytics
Toggle(isOn: viewStore.binding(
As most of the user decisions are client side no history is available. Only the current state can be inspected.
O.Purp_7
Most libraries are self written and cover only what is needed. Dependencies are only included if really necessary. We think about including only sub packages, but as most dependencies are very small, this is hardly used. Besides that, no means of removing unused dependency functionality is available for the platform.
O.Purp_8
../Sources/eRpApp/Session/KeychainStorage.swift:23
Implementation of data storage that is
// [REQ:gemSpec_eRp_FdV:A_19186]
// [REQ:gemSpec_eRp_FdV:A_19188] Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_IDP_Frontend:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemF_Tokenverschlüsselung:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Implementation
// [REQ:BSI-eRp-ePA:O.Purp_8#1,O.Arch_2#5,O.Arch_4#3,O.Source_7#2,O.Data_2#2] Implementation of data storage that is
// persisted via keychain
class KeychainStorage: SecureUserDataStore, IDPStorage, SecureEGKCertificateStorage {
../Sources/eRpLocalStorage/CoreDataControllerFactory.swift:32
CoreData databases are protected
// [REQ:BSI-eRp-ePA:O.Purp_8#2,O.Arch_2#2] CoreData databases are protected
self.fileProtection = fileProtection
../Sources/eRpLocalStorage/CoreDataController.swift:60
actual protection setting on file system level
// [REQ:BSI-eRp-ePA:O.Purp_8#3,O.Arch_2#3] actual protection setting on file system level
protectedStoreDescription.setOption(
../Sources/eRpLocalStorage/CoreDataController.swift:84
Database backup exclusion
// [REQ:BSI-eRp-ePA:O.Purp_8#4,O.Arch_2#4,O.Arch_4#2,O.Data_15#1] Database backup exclusion
if excludeFromBackup, let storeUrl = store.url {
O.Purp_9
../Sources/eRpApp/Screens/Settings/IDPTokenView/IDPTokenView.swift:10
Access and SSO Token display
// [REQ:BSI-eRp-ePA:O.Purp_9#1] Access and SSO Token display
// [REQ:BSI-eRp-ePA:O.Tokn_5#3] Access and SSO Token display
struct IDPTokenView: View {
O.Rand_1
We use the platform provided secure random generator. See Apple Support Website for details.
../Sources/IDP/internal/SecKeyRandom.swift:21
Secure Random generator.
/// Generate random Data with given length
///
/// [REQ:gemSpec_Krypt:GS-A_4367]
/// [REQ:BSI-eRp-ePA:O.Rand_1#2] Secure Random generator.
///
/// - Parameters:
/// - length: the number of bytes to generate
/// - randomizer: the randomizer to be used. Default: kSecRandomDefault
/// - Returns: the random initialized Data
/// - Throws: `IDPError`
public func generateSecureRandom(length: Int, randomizer: SecRandomRef? = kSecRandomDefault) throws -> Data {
../Sources/VAUClient/internal/VAURandom.swift:22
Secure Random generator.
/// Generate random Data with given length
///
/// [REQ:gemSpec_Krypt:GS-A_4367]
/// [REQ:BSI-eRp-ePA:O.Rand_1#3] Secure Random generator.
///
/// - Parameters:
/// - length: the number of bytes to generate
/// - randomizer: the randomizer to be used. Default: kSecRandomDefault
/// - Returns: the random initialized Data
/// - Throws: `VAUError`
static func generateSecureRandom(length: Int, randomizer: SecRandomRef? = kSecRandomDefault) throws -> Data {
O.Rand_2
We use the platform provided secure random generator. See Apple Support Website for details.
O.Rand_3
We use the platform provided secure random generator. See Apple Support Website for details.
O.Rand_4
We use the platform provided secure random generator. See Apple Support Website for details.
O.Resi_1
The onboarding contains defaults that are best practices, such as using biometrics for authentication. While using the Cardwall, selecting the “save login” option results in an dialog with additional information, to let the user make informed decisions. A missing device passcode results in a dialog that is recommending the setup of a device passcode.
../Sources/eRpApp/Screens/Onboarding/OnboardingRegisterAuthenticationView.swift:10
View containing onboarding authentication
// [REQ:BSI-eRp-ePA:O.Resi_1#2] View containing onboarding authentication
struct OnboardingRegisterAuthenticationView: View, KeyboardReadable {
../Sources/eRpApp/Screens/CardWall/Login/CardWallLoginOptionView.swift:118
View containing information regarding the login options.
// [REQ:gemSpec_IDP_Frontend:A_21574] Actual view
// [REQ:BSI-eRp-ePA:O.Resi_1#3] View containing information regarding the login options.
struct PrivacyWarningViewContainer: View {
../Sources/eRpApp/Screens/Main/DeviceSecurity/DeviceSecurityNoSystemPin/DeviceSecuritySystemPinView.swift:9
View containing the “no system pin” message.
// [REQ:BSI-eRp-ePA:O.Resi_1#4] View containing the "no system pin" message.
struct DeviceSecuritySystemPinView: View {
O.Resi_2
Jailbreak detection is done on startup of the application after the app authentication. If a jailbreak is detected, a information dialog is presented to the user.
../Sources/eRpApp/Screens/Main/MainView.swift:120
trigger device security check
// [REQ:BSI-eRp-ePA:O.Arch_6#2,O.Resi_2#2,O.Plat_1#2] trigger device security check
viewStore.send(.loadDeviceSecurityView)
../Sources/eRpApp/Session/Helper/DeviceSecurityManager.swift:58
calculate system risk for jailbreak and missing device pin
// [REQ:BSI-eRp-ePA:O.Arch_6#3,O.Resi_2#3,O.Plat_1#3] calculate system risk for jailbreak and missing device pin
var showSystemSecurityWarning: AnyPublisher<DeviceSecurityWarningType, Never> {
../Sources/eRpApp/Session/Helper/DeviceSecurityManager.swift:112
Jailbreak detection
// [REQ:BSI-eRp-ePA:O.Arch_6#4,O.Resi_2#4] Jailbreak detection
func informJailbreakDetected() -> Bool {
../Sources/eRpApp/Screens/Main/DeviceSecurity/DeviceSecurityRootedDevice/DeviceSecurityRootedDeviceView.swift:8
Jailbreak information view
// [REQ:BSI-eRp-ePA:O.Arch_6#5,O.Resi_2#5] Jailbreak information view
struct DeviceSecurityRootedDeviceView: View {
O.Resi_3
Starting within a debug environment is only possible on rooted/jailbroken devices. If that is the case, the user sees a dialog and can choose to continue or cancel using the application. See O.Resi_2 for jailbreak detection details.
O.Resi_4
Starting with unusal app rights is only possible on rooted/jailbroken devices. If that is the case, the user sees a dialog and can choose to continue or cancel using the application. See O.Resi_2 for jailbreak detection details.
O.Resi_5
Device Integrity may only be compromised on jailbroken devices. If that is the case, the user sees a dialog and can choose to continue or cancel using the application. See O.Resi_2 for jailbreak detection details. We exclude very old and unsecure devices, by restricting the application to modern iOS Versions. That restriction is automatically restricting the range of devices it can run on. See options.deploymentTarget.iOS
path within project.yml
or the generated IPHONEOS_DEPLOYMENT_TARGET
within the project.pbxproj
file for the iOS Version restriction.
O.Resi_6
IDP Communication is secured by using TLS with pinned Certificates as well as JWEs with keys that are pinned using a TI specific trust anchor as well as OCSP. FD Communication is secured by using TLS and VAU encryption, again with pinned Certificates for both methods. APOVZD communication uses TLS with pinned certificates. The Pinned certificates fingerprints can be found within Sources/eRpApp/Resources/Info.plist
.
../Sources/IDP/DefaultIDPSession.swift:261
Implementation of loading the discovery document for fetching signature and
// [REQ:BSI-eRp-ePA:O.Resi_6#2] Implementation of loading the discovery document for fetching signature and
// encryption keys.
private func loadDiscoveryDocument() -> AnyPublisher<DiscoveryDocument, IDPError> {
../Sources/IDP/DefaultIDPSession.swift:420
Discovery Document signature verification
// [REQ:gemSpec_IDP_Sek:A_22296] Signature verification
// [REQ:BSI-eRp-ePA:O.Resi_6#3] Discovery Document signature verification
guard try jwtContainer.verify(with: document.discKey) == true else {
../Sources/IDP/DefaultIDPSession.swift:290
Discovery Document signature verification
// Validate JWT/DiscoveryDocument signature
// [REQ:gemSpec_Krypt:A_17207] Only implemented for brainpoolP256r1
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02] Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4361-02] Assure that brainpoolP256r1 is used
// [REQ:BSI-eRp-ePA:O.Resi_6#4] Discovery Document signature verification
guard (try? fetchedDocument.backing.verify(with: fetchedDocument.discKey)) ?? false else {
../Sources/IDP/DefaultIDPSession.swift:775
Discovery Document signature verification
// [REQ:BSI-eRp-ePA:O.Resi_6#5] Discovery Document signature verification
validate(certificate: discoveryDocument.discKey)
O.Resi_7
Recent Jailbreaks force a reboot or restart of springboard. Thus the application will also be restarted in these cases. The check for hardening is done on app startup and should be sufficient.
O.Resi_8
The application source is available on github. Any measure of preventing reverse engineering would be pointless.
O.Resi_9
Data is not stored while moving between platforms or devices. Private keys are stored within the secure enclave an cannot leave the device.
O.Resi_10
Missing internet connection or other means of disruption use the same error mechanisms as every part of the app uses for normal errors. Data is only deleted where appropriate, e.g. invalid tokens are deleted. User Data is not deleted unless explicitly confirmed by the user.
O.Sess_1
TLS Session handling is done via NSURLSession. Cookie based user sessions are never created, all connections are ephemeral, remote APIs are stateless.
../Sources/HTTPClient/DefaultHTTPClient.swift:42
URLSession is used as Network Framework
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
O.Sess_2
not applicable
O.Sess_3
not applicable
O.Sess_4
not applicable
O.Sess_5
not applicable
O.Sess_6
not applicable
O.Sess_7
not applicable
O.Source_1
../Sources/eRpApp/Screens/CameraView/ErxTaskScannerView.swift:28
Scanning tasks starts with scanner callback
// [REQ:BSI-eRp-ePA:O.Purp_2#1,O.Data_6#3] Scanning tasks contains purpose related data input
// [REQ:BSI-eRp-ePA:O.Source_1#1] Scanning tasks starts with scanner callback
viewStore.send(.analyse(scanOutput: $0))
../Sources/eRpApp/Screens/CameraView/ScannerDomain.swift:133
analyse the input
// [REQ:BSI-eRp-ePA:O.Source_1#2] analyse the input
let result = try CodeAnalyser.analyse(scanOutput: scanOutput, with: state.acceptedTaskBatches)
../Sources/eRpApp/Screens/CameraView/ScannerDomain+Helper.swift:16
validation
// [REQ:BSI-eRp-ePA:O.Source_1#3] validation
static func analyse(scanOutput: [ScanOutput],
../Sources/eRpKit/ScannedErxTask.swift:104
actual validation by decoding into predefined structure
// [REQ:gemSpec_eRp_FdV:A_19984] validate data matrix code structure
// [REQ:BSI-eRp-ePA:O.Source_1#4] actual validation by decoding into predefined structure
erxToken = try jsonDecoder.decode(ErxToken.self, from: jsonData)
../Sources/eRpApp/SceneDelegate.swift:232
External application calls via Universal Linking
// [REQ:BSI-eRp-ePA:O.Source_1#5] External application calls via Universal Linking
func scene(_: UIScene, continue userActivity: NSUserActivity) {
../Sources/eRpApp/Screens/AppStartDomain.swift:161
External application calls via Universal Linking
// [REQ:BSI-eRp-ePA:O.Source_1#6] External application calls via Universal Linking
case let .universalLink(url):
../Sources/eRpApp/Screens/Main/MainDomain.swift:183
redirect into correct domain
// [REQ:BSI-eRp-ePA:O.Source_1#7] redirect into correct domain
return .run { send in
../Sources/eRpApp/Screens/Main/ExtAuth/ExtAuthPendingDomain.swift:163
Validate data by parsing url and only allowing predefined variables as String
// [REQ:BSI-eRp-ePA:O.Source_1#8] Validate data by parsing url and only allowing predefined variables as String
case let .externalLogin(url),
O.Source_2
Data escaping is done by the OS Frameworks. We never generate manual SQL Queries, CoreData is used as an ORM, Queries are build with NSFetchRequests and NSPredicates that escape all manual input. Actual user data is transported via FHIR and escaping is handled by the used FHIR library provided by Apple.
../Sources/eRpLocalStorage/Pharmacy/PharmacyCoreDataStore.swift:13
CoreDataStore adapter for PharmacyLocation
s
/// Store for fetching, creating, updating or deleting `PharmacyLocation`s on the provided `CoreDataController`
/// [REQ:BSI-eRp-ePA:O.Source_2#2] CoreDataStore adapter for `PharmacyLocation`s
public class PharmacyCoreDataStore: PharmacyLocalDataStore, CoreDataCrudable {
../Sources/eRpLocalStorage/Prescriptions/ErxTaskCoreDataStore.swift:20
CoreDataStore adapter for ErxTask
s
// tag::ErxTaskCoreDataStoreDescription[]
/// Store for fetching, creating, updating or deleting `ErxTask`s and it‘s underlying types. Access to most entities is
/// tied to the given profileId.
/// [REQ:BSI-eRp-ePA:O.Source_2#3] CoreDataStore adapter for `ErxTask`s
public class DefaultErxTaskCoreDataStore: ErxTaskCoreDataStore {
../Sources/eRpLocalStorage/Profiles/ProfileCoreDataStore.swift:13
CoreDataStore adapter for Profile
s
/// Store for fetching, creating, updating or deleting `Profile`s on the provided `CoreDataController`
/// [REQ:BSI-eRp-ePA:O.Source_2#4] CoreDataStore adapter for `Profile`s
public class ProfileCoreDataStore: ProfileDataStore, CoreDataCrudable {
../Sources/eRpLocalStorage/ShipmentInfo/ShipmentInfoCoreDataStore.swift:13
CoreDataStore adapter for ShipmentInfoEntity
s
/// Store for fetching, creating, updating or deleting `ShipmentInfoEntity`s on the provided `CoreDataController`
/// [REQ:BSI-eRp-ePA:O.Source_2#5] CoreDataStore adapter for `ShipmentInfoEntity`s
public class ShipmentInfoCoreDataStore: ShipmentInfoDataStore, CoreDataCrudable {
../Sources/eRpLocalStorage/AVSTransaction/AVSTransactionCoreDataStore.swift:13
CoreDataStore adapter for AVSTransactionEntity
s
/// Store for fetching, creating, updating or deleting `AVSTransactionEntity`s on the provided `CoreDataController`
/// [REQ:BSI-eRp-ePA:O.Source_2#6] CoreDataStore adapter for `AVSTransactionEntity`s
public class AVSTransactionCoreDataStore: AVSTransactionDataStore, CoreDataCrudable {
../Sources/Pharmacy/ModelsR4.Bundle+Location.swift:18
Extension containing all FHIR related parsing for Pharmacies
/// [REQ:BSI-eRp-ePA:O.Source_2#7] Extension containing all FHIR related parsing for Pharmacies
extension ModelsR4.Bundle {
See all files named ModelsR4.Bundles+<TypeName>
within eRpRemoteStorage
Package to find all references for FD related FHIR parsing. All other files within the Module are either helpers or provide support for creating FHIR objects that may be sent to the FD.
O.Source_3
Error messages are localized using the Foundation.LocalizedError
protocol. Search for LocalizedError
to see all instances. Most errors are localized with static text, some errors contain server side error messages. User data is never used for error messages. Logging is only active on debug builds.
O.Source_4
Exception handling in swift uses Errors to represent the exception. We use a custom protocol CodedError
that is autogenerated for all error messages. Together with LocalizedError
we create error messages that contain a user readable description as well as some technical identifiers to easily identify specific error scenarios and give better support. See CodedError.swift
and CodedError.generated.swift
for the protocol and the autogenerated implementations of it.
O.Source_5
As exceptions and errors are kind of the same construct in swift, this aspect is hard to answer. If a server responds with 401/403 we delete tokens or other security measures, because they are no longer valid anyways. While logging in via biometrics we create temporary data containers for the user certificate and key identifier, that are deleted if the process is not completed properly and kept if the process completes.
../Sources/IDP/IDPInterceptor.swift:72
invalidate/delete unauthorized token
// [REQ:gemSpec_eRp_FdV:A_20167] invalidate/delete unauthorized token
// [REQ:BSI-eRp-ePA:O.Source_5#2] invalidate/delete unauthorized token
self.session.invalidateAccessToken()
../Sources/IDP/DefaultIDPSession.swift:107
Invalidation means deleting the token
// [REQ:BSI-eRp-ePA:O.Source_5#3] Invalidation means deleting the token
storage.set(token: nil)
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:115
Creation of the pairing session
// [REQ:BSI-eRp-ePA:O.Source_5#4] Creation of the pairing session
pairingSession = try sessionProvider.signatureProvider(for: profileID).createPairingSession()
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:209
Failure will delete paring data
// [REQ:gemSpec_IDP_Frontend:A_21598,A_21595] Failure will delete paring data
// [REQ:BSI-eRp-ePA:O.Source_5#5] Failure will delete paring data
_ = try? sessionProvider.signatureProvider(for: profileID).abort(pairingSession: pairingSession)
O.Source_6
Swift uses Automatic Reference Counting that does not require active memory management. Raw memory access is possible but only used for swift-OpenSSL dependency that wraps the c library.
O.Source_7
All sensitive data is handled via reference types. After usage the swift runtime handles deletion of the in memory representation. Secure enclave encryption keys can never leave the secure enclave.
../Sources/eRpApp/Session/KeychainStorage.swift:23
Implementation of data storage that is
// [REQ:gemSpec_eRp_FdV:A_19186]
// [REQ:gemSpec_eRp_FdV:A_19188] Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_IDP_Frontend:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemF_Tokenverschlüsselung:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Implementation
// [REQ:BSI-eRp-ePA:O.Purp_8#1,O.Arch_2#5,O.Arch_4#3,O.Source_7#2,O.Data_2#2] Implementation of data storage that is
// persisted via keychain
class KeychainStorage: SecureUserDataStore, IDPStorage, SecureEGKCertificateStorage {
O.Source_8
We use swift macros to remove development code. Configuration of server Environments is done within AppConfiguration.swift
. A debug menu is available within settings, rooted within SettingsView.swift
.
../Sources/eRpApp/Screens/Settings/SettingsView.swift:36
Debug menu is only visible on debug builds
// [REQ:BSI-eRp-ePA:O.Source_8#3] Debug menu is only visible on debug builds
#if ENABLE_DEBUG_VIEW
../Sources/eRpApp/Screens/Settings/SettingsView.swift:267
DebugSectionView is only available on debug builds
// [REQ:BSI-eRp-ePA:O.Source_8#4] DebugSectionView is only available on debug builds
private struct DebugSectionView: View {
../Sources/eRpApp/Screens/DebugView/DebugView.swift:13
DebugView is only available on debug builds
// [REQ:BSI-eRp-ePA:O.Source_8#5] DebugView is only available on debug builds
struct DebugView: View {
O.Source_9
We use swift macros to remove debug mechanism code. A debug menu is available within settings, rooted within SettingsView.swift
. All debug classes are excluded from release builds by using swift macros to remove the whole classes/structs.
O.Source_10
We use common defaults for all compiler security related settings. See Xcode build configuration for eRpApp Target using current Xcode for actual settings.
O.Source_11
We do not create any kind of logs for release builds.
O.Tokn_1
The token is stored within the keychain for long lasting tokens, short living tokens as used in biometric pairing process are not persisted and only live in the memory.
../Sources/IDP/IDPStorage.swift:37
protocol defintion for token storage
/// Set and save the IDPToken
/// [REQ:BSI-eRp-ePA:O.Tokn_1#2] protocol defintion for token storage
func set(token: IDPToken?)
../Sources/eRpApp/Session/MemoryStorage.swift:41
MemoryStorage implementation
/// [REQ:BSI-eRp-ePA:O.Tokn_1#3] MemoryStorage implementation
func set(token: IDPToken?) {
../Sources/eRpApp/Session/KeychainStorage.swift:127
KeychainStorage implementation
// [REQ:gemSpec_eRp_FdV:A_20184]
// [REQ:gemSpec_eRp_FdV:A_21328#3] KeychainStorage implementation
// [REQ:BSI-eRp-ePA:O.Tokn_1#4] KeychainStorage implementation
let success: Bool
../Sources/IDP/DefaultIDPSession.swift:235
Storing the token from a successfull login
// [REQ:BSI-eRp-ePA:O.Tokn_1#5] Storing the token from a successfull login
self?.storage.set(token: token)
O.Tokn_2
The token is created by the backend, we have no means of manipulating the content.
O.Tokn_3
The token is created by the backend, we have no means of manipulating the content.
O.Tokn_4
The token is created by the IDP and signed there. We have not valid signing identity within the application to sign the token.
O.Tokn_5
A Section within User Profiles contains all tokens for that profile on the device
../Sources/eRpApp/Screens/Settings/Profiles/Edit/EditProfileView.swift:423
Section for Token display
// [REQ:BSI-eRp-ePA:O.Tokn_5#2] Section for Token display
private struct TokenSectionView: View {
../Sources/eRpApp/Screens/Settings/IDPTokenView/IDPTokenView.swift:11
Access and SSO Token display
// [REQ:BSI-eRp-ePA:O.Purp_9#1] Access and SSO Token display
// [REQ:BSI-eRp-ePA:O.Tokn_5#3] Access and SSO Token display
struct IDPTokenView: View {
O.Tokn_6
A logout button is available within each user profile.
../Sources/eRpApp/Screens/Settings/Profiles/Edit/EditProfileView.swift:217
Logout Button
// [REQ:BSI-eRp-ePA:O.Tokn_6#2] Logout Button
Button(action: {
../Sources/eRpApp/Screens/Settings/Profiles/Edit/EditProfileDomain.swift:281
Call the token removal upon manual logout
// [REQ:gemSpec_IDP_Frontend:A_20499-01#1] Call the SSO_TOKEN removal upon manual logout
// [REQ:BSI-eRp-ePA:O.Tokn_6#3] Call the token removal upon manual logout
return .run { [profileId = state.profileId] _ in
O.TrdP_1
A list of all active used dependencies can be found within dependencies.yml
. The list contains dependencies from SwiftPM.
O.TrdP_2
SAST Scans are also including external libraries.
O.TrdP_3
Library updates are done by a manual periodic process. If any security issues arise and no fix is expected to be provided, a manual fork of external libraries is implemented.
O.TrdP_4
Actual dependencies can be found within eRp-App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
and Cartfile.resolved
containing pinned versions with corresponding hashes. Library updates always require manual interaction to update the *.resolved
files. We use very few providers of libraries and try to avoid any unnecessary dependency. Each providing party/dependency is evaluated by looking at GitHub metrics like open issues, stars and activity in general.
O.TrdP_5
We do not share sensitive data with third parties. Se data usages within Purp_8
and O.Arch_2
.
O.TrdP_6
See O.Source_1
and O.Arch_6
.
O.TrdP_7
As most libraries are source code dependencies, these scans are part of SAST Scanning. Libraries that are not source code dependencies are the precompiled OpenSSL and OpenHealthCardKit (own library but different repository). All third party libraries are mirrored into internal repositories. If necessary we fork libraries to apply fixes.
gemF_Biometrie
A_21415
Pairing_Data ../Sources/IDP/Models/PairingData.swift:12
/// Structure for registering a biometric key. See `SignedPairingData` for sigend representation.
/// [REQ:gemF_Biometrie:A_21415:Pairing_Data]
public struct PairingData: Claims, Codable {
Registration_Data ../Sources/IDP/Models/RegistrationData.swift:13
/// Bundles data needed for creating and verifiying a pairing.
/// [REQ:gemF_Biometrie:A_21415:Registration_Data]
/// [REQ:gemSpec_IDP_Frontend:A_21416] Data Structure
public struct RegistrationData: Claims, Codable {
Device_Information ../Sources/IDP/Models/RegistrationData.swift:39
/// [REQ:gemF_Biometrie:A_21415:Device_Information]
public struct DeviceInformation: Codable {
Device_Type ../Sources/IDP/Models/RegistrationData.swift:56
/// [REQ:gemF_Biometrie:A_21415:Device_Type]
/// [REQ:gemSpec_IDP_Frontend:A_21591]
public struct DeviceType: Codable {
Encrypted_Registration_Data ../Sources/IDP/Models/RegistrationData.swift:104
Returns JWE encrypted Registration_Data
/// [REQ:gemF_Biometrie:A_21415:Encrypted_Registration_Data] Returns JWE encrypted Registration_Data
/// [REQ:gemSpec_IDP_Frontend:A_21416] Encryption
func encrypted(with publicKey: BrainpoolP256r1.KeyExchange.PublicKey,
Signed_Pairing_Data ../Sources/IDP/Models/SignedPairingData.swift:12
/// Signed (with eGK) version of `PairingData`.
/// [REQ:gemF_Biometrie:A_21415:Signed_Pairing_Data]
public struct SignedPairingData {
A_21450
Pairing_Entry ../Sources/IDP/Models/PairingEntry.swift:12
/// Represents stored data within the idp.
/// [REQ:gemF_Biometrie:A_21450:Pairing_Entry]
public struct PairingEntry: Equatable, Codable {
gemF_Tokenverschlüsselung
A_20526-01
../Sources/IDP/DefaultIDPSession.swift:153
Encryption with JWE
// [REQ:gemF_Tokenverschlüsselung:A_20526-01] Encryption with JWE
guard let jwe = try? signedChallenge.encrypt(with: document.encryptionPublicKey,
../Sources/IDP/Models/IDPChallengeSession.swift:86
Embed certificate
// [REQ:gemF_Tokenverschlüsselung:A_20526-01] Embed certificate
let header = JWT.Header(alg: alg, x5c: certificates, typ: "JWT", cty: "NJWT")
../Sources/IDP/internal/RealIDPClient.swift:146
Building and sending the request
// [REQ:gemF_Tokenverschlüsselung:A_20526-01] Building and sending the request
var request = URLRequest(url: document.authentication.url, cachePolicy: .reloadIgnoringCacheData)
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:140
Smartcard signature
// [REQ:gemSpec_IDP_Frontend:A_20700-07] C.CH.AUT
// [REQ:gemF_Tokenverschlüsselung:A_20526-01] Smartcard signature
// [REQ:gemF_Tokenverschlüsselung:A_20700-05,A_20700-06] sign
.sign(challengeSession: challengeSession)
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:426
Smartcard signature
// [REQ:gemSpec_IDP_Frontend:A_20700-07] C.CH.AUT
// [REQ:gemF_Tokenverschlüsselung:A_20526-01] Smartcard signature
// [REQ:gemF_Tokenverschlüsselung:A_20700-06] sign
.readAutCertificate()
A_20700-05
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:141
sign
// [REQ:gemSpec_IDP_Frontend:A_20700-07] C.CH.AUT
// [REQ:gemF_Tokenverschlüsselung:A_20526-01] Smartcard signature
// [REQ:gemF_Tokenverschlüsselung:A_20700-05,A_20700-06] sign
.sign(challengeSession: challengeSession)
A_20700-06
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:141
sign
// [REQ:gemSpec_IDP_Frontend:A_20700-07] C.CH.AUT
// [REQ:gemF_Tokenverschlüsselung:A_20526-01] Smartcard signature
// [REQ:gemF_Tokenverschlüsselung:A_20700-05,A_20700-06] sign
.sign(challengeSession: challengeSession)
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:320
sign
// [REQ:gemSpec_IDP_Frontend:A_20526-01] sign
// [REQ:gemF_Tokenverschlüsselung:A_20700-06] sign
func sign(can: String, pin: String, challenge: IDPChallengeSession)
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:427
sign
// [REQ:gemSpec_IDP_Frontend:A_20700-07] C.CH.AUT
// [REQ:gemF_Tokenverschlüsselung:A_20526-01] Smartcard signature
// [REQ:gemF_Tokenverschlüsselung:A_20700-06] sign
.readAutCertificate()
A_21322
../Sources/eRpApp/Session/KeychainStorage.swift:21
Storage implementation uses iOS Keychain
// [REQ:gemSpec_eRp_FdV:A_19186]
// [REQ:gemSpec_eRp_FdV:A_19188] Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_IDP_Frontend:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemF_Tokenverschlüsselung:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Implementation
// [REQ:BSI-eRp-ePA:O.Purp_8#1,O.Arch_2#5,O.Arch_4#3,O.Source_7#2,O.Data_2#2] Implementation of data storage that is
// persisted via keychain
class KeychainStorage: SecureUserDataStore, IDPStorage, SecureEGKCertificateStorage {
gemSpec_FD_eRp
A_19145
../Sources/eRpApp/Screens/Main/PrescriptionList/Model/Prescription.swift:209
prevent deletion while task is in progress
// [REQ:gemSpec_FD_eRp:A_19145] prevent deletion while task is in progress
return erxTask.status != .inProgress
A_21267
../Sources/eRpApp/Screens/Main/PrescriptionList/Model/Prescription.swift:29
direct assignment
// [REQ:gemSpec_FD_eRp:A_21267] direct assignment
var type: PrescriptionType = .regular
A_21360
../Sources/eRpApp/Screens/Main/PrescriptionList/Model/Prescription.swift:173
no redeem informations available for flowtype 169
// [REQ:gemSpec_FD_eRp:A_21360] no redeem informations available for flowtype 169
guard type != .directAssignment
A_22102
../Sources/eRpApp/Screens/Main/PrescriptionList/Model/Prescription.swift:201
prevent deletion of tasks with flowtype 169 while not completed
// [REQ:gemSpec_FD_eRp:A_22102] prevent deletion of tasks with flowtype 169 while not completed
guard type != .directAssignment
gemSpec_IDP_Frontend
A_19908-01
../Sources/IDP/DefaultIDPSession.swift:652
Signature check
// [REQ:gemSpec_Krypt:A_17207] Only implemented for brainpoolP256r1
// [REQ:gemSpec_IDP_Frontend:A_19908-01] Signature check
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let verified = try? challenge.challenge.verify(with: document.authentication.cert),
A_19937
../Sources/IDP/Models/IDPError.swift:85
Error formatting
// [REQ:gemSpec_IDP_Frontend:A_19937,A_20605,A_20085] Error formatting
public var description: String {
../Sources/IDP/internal/RealIDPClient.swift:517
Decoding server errors
// [REQ:gemSpec_IDP_Frontend:A_19937,A_20605] Decoding server errors
private static func responseError(for body: Data) -> IDPError {
../Sources/eRpApp/Common/ErrorLocalization/IDPError+Localization.swift:23
Localized description of server errors
// [REQ:gemSpec_IDP_Frontend:A_19937,A_20605,A_20085] Localized description of server errors
case let .internal(error: error): return error.localizedDescription
A_19938-01
../Sources/IDP/DefaultIDPSession.swift:203
Decrypt, fails if wrong aes key
// [REQ:gemSpec_IDP_Frontend:A_19938-01,A_20283-01] Decrypt, fails if wrong aes key
guard let decrypted = try? token.decrypted(with: self.cryptoBox.aesKey) else {
../Sources/IDP/DefaultIDPSession.swift:226
Usage
idToken: decrypted.idToken, // [REQ:gemSpec_IDP_Frontend:A_19938-01] Usage
ssoToken: exchange.sso,
A_20068-01
: Implemented with ATS within Info.plist
, path: NSAppTransportSecurity.NSPinnedDomains
A_20079
../Sources/IDP/internal/RealIDPClient.swift:263
Network timeouts will traverse the queue as HTTPError
s.
// [REQ:gemSpec_IDP_Frontend:A_20079] Network timeouts will traverse the queue as `HTTPError`s.
$0.asIDPError()
A_20085
../Sources/IDP/Models/IDPError.swift:85
Error formatting
// [REQ:gemSpec_IDP_Frontend:A_19937,A_20605,A_20085] Error formatting
public var description: String {
../Sources/eRpApp/Common/ErrorLocalization/IDPError+Localization.swift:13
Error localization is not done yet, this is the place to localize
// [REQ:gemSpec_IDP_Frontend:A_20085] Error localization is not done yet, this is the place to localize
// accordingly.
switch self {
../Sources/eRpApp/Common/ErrorLocalization/IDPError+Localization.swift:23
Localized description of server errors
// [REQ:gemSpec_IDP_Frontend:A_19937,A_20605,A_20085] Localized description of server errors
case let .internal(error: error): return error.localizedDescription
A_20283-01
../Sources/IDP/DefaultIDPSession.swift:203
Decrypt, fails if wrong aes key
// [REQ:gemSpec_IDP_Frontend:A_19938-01,A_20283-01] Decrypt, fails if wrong aes key
guard let decrypted = try? token.decrypted(with: self.cryptoBox.aesKey) else {
../Sources/IDP/DefaultIDPSession.swift:224
Usage
accessToken: decrypted.accessToken, // [REQ:gemSpec_IDP_Frontend:A_20283-01] Usage
expires: self.time().addingTimeInterval(TimeInterval(token.expiresIn)),
A_20309
../Sources/IDP/DefaultIDPSession.swift:630
generation and hashing for codeChallenge
// Generate a verifierCode
// [REQ:gemSpec_IDP_Frontend:A_20309] generation and hashing for codeChallenge
guard let verifierCode = try? self.cryptoBox.generateRandomVerifier(),
../Sources/IDP/internal/IDPCrypto.swift:67
verifierLength is 32 bytes, encoded to base64 this results in 43 chars
// [REQ:gemSpec_IDP_Frontend:A_20309] verifierLength is 32 bytes, encoded to base64 this results in 43 chars
// (32 * 4 / 3 = 42,6)
try randomGenerator(verifierLength).encodeBase64urlsafe().utf8string
A_20483
../Sources/IDP/DefaultIDPSession.swift:638
// [REQ:gemSpec_IDP_Frontend:A_20483]
return self.client.requestChallenge(
A_20499
Not applicable as authenticator module is within FdV, not 3rd party app.
../Sources/eRpApp/Session/KeychainStorage.swift:248
Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_IDP_Frontend:A_20499,A_20499-1#3] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_eRp_FdV:A_20186] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
set(can: nil)
../Sources/eRpApp/Session/ProfileSecureDataWiper.swift:39
Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_IDP_Frontend:A_20499,A_20499-01#2] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_eRp_FdV:A_20186] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_IDP_Frontend:A_21603] Certificate
storage.wipe()
A_20499-01
../Sources/eRpApp/Screens/Settings/Profiles/Edit/EditProfileDomain.swift:280
Call the SSO_TOKEN removal upon manual logout
// [REQ:gemSpec_IDP_Frontend:A_20499-01#1] Call the SSO_TOKEN removal upon manual logout
// [REQ:BSI-eRp-ePA:O.Tokn_6#3] Call the token removal upon manual logout
return .run { [profileId = state.profileId] _ in
../Sources/eRpApp/Session/ProfileSecureDataWiper.swift:39
Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_IDP_Frontend:A_20499,A_20499-01#2] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_eRp_FdV:A_20186] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_IDP_Frontend:A_21603] Certificate
storage.wipe()
A_20499-1
../Sources/eRpApp/Session/KeychainStorage.swift:248
Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_IDP_Frontend:A_20499,A_20499-1#3] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_eRp_FdV:A_20186] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
set(can: nil)
A_20512
../Sources/IDP/DefaultIDPSession.swift:277
// [REQ:gemSpec_IDP_Frontend:A_20512]
self.storage.set(discovery: nil)
../Sources/IDP/Models/DiscoveryDocument.swift:180
// [REQ:gemSpec_IDP_Frontend:A_20512]
func isValid(on date: Date) -> Bool {
A_20525
Not applicable as authenticator module is within FdV, not 3rd party app
Not applicable as authenticator module is within FdV. Consent is given by using the app.
A_20526-01
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.swift:407
sign and verify with idp
// [REQ:gemSpec_eRp_FdV:A_20172]
// [REQ:gemSpec_IDP_Frontend:A_20526-01] sign and verify with idp
func signChallengeWithNFCCard(can: String,
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.swift:441
verify with idp
// [REQ:gemSpec_eRp_FdV:A_20172]
// [REQ:gemSpec_IDP_Frontend:A_20526-01] verify with idp
private func verifyResultWithIDP(_ signedChallenge: SignedChallenge,
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:319
sign
// [REQ:gemSpec_IDP_Frontend:A_20526-01] sign
// [REQ:gemF_Tokenverschlüsselung:A_20700-06] sign
func sign(can: String, pin: String, challenge: IDPChallengeSession)
A_20527
Not directly applicable as authenticator module is within FdV, not 3rd party app. AUTHORIZATION_CODE will be used directly.
../Sources/IDP/DefaultIDPSession.swift:166
Returning the AUTHORIZATION_CODE
// [REQ:gemSpec_IDP_Frontend:A_20527] Returning the AUTHORIZATION_CODE
return Just(exchangeToken).setFailureType(to: IDPError.self).eraseToAnyPublisher()
A_20529-01
../Sources/IDP/DefaultIDPSession.swift:184
Encryption
// [REQ:gemSpec_IDP_Frontend:A_20529-01] Encryption
// [REQ:gemSpec_IDP_Frontend:A_21323,A_21324#1] Crypto box contains `Token-Key`
guard let encryptedKeyVerifier = try? KeyVerifier(
A_20600
../Sources/IDP/internal/RealIDPClient.swift:173
// [REQ:gemSpec_IDP_Frontend:A_20600]
return IDPExchangeToken(
../Sources/IDP/internal/RealIDPClient.swift:502
// [REQ:gemSpec_IDP_Frontend:A_20600]
return IDPExchangeToken(code: code,
A_20601
../Sources/IDP/internal/RealIDPClient.swift:101
Transfer
// [REQ:gemSpec_IDP_Frontend:A_20603,A_20601,A_20601-01] Transfer
URLQueryItem(name: "client_id", value: clientConfig.clientId.urlPercentEscapedString()),
A_20601-01
../Sources/IDP/internal/RealIDPClient.swift:101
Transfer
// [REQ:gemSpec_IDP_Frontend:A_20603,A_20601,A_20601-01] Transfer
URLQueryItem(name: "client_id", value: clientConfig.clientId.urlPercentEscapedString()),
A_20602
../Sources/IDP/IDPInterceptor.swift:56
// [REQ:gemSpec_IDP_Frontend:A_20602,A_21325#1]
request.setValue("\(token.tokenType) \(token.accessToken)", forHTTPHeaderField: "Authorization")
A_20603
../Sources/IDP/internal/RealIDPClient.swift:101
Transfer
// [REQ:gemSpec_IDP_Frontend:A_20603,A_20601,A_20601-01] Transfer
URLQueryItem(name: "client_id", value: clientConfig.clientId.urlPercentEscapedString()),
../Sources/IDP/internal/RealIDPClient.swift:249
transfer
// [REQ:gemSpec_IDP_Frontend:A_20603] transfer
"client_id": clientConfig.clientId,
../Sources/eRpApp/AppConfiguration.swift:15
Actual ID
// [REQ:gemSpec_IDP_Frontend:A_20603] Actual ID
private static let defaultClientId: String = "eRezeptApp"
../Sources/eRpApp/AppConfiguration.swift:59
Actual ID
// clientId
// [REQ:gemSpec_IDP_Frontend:A_20603] Actual ID
let clientId: String = defaultClientId
A_20605
../Sources/IDP/Models/IDPError.swift:85
Error formatting
// [REQ:gemSpec_IDP_Frontend:A_19937,A_20605,A_20085] Error formatting
public var description: String {
../Sources/IDP/internal/RealIDPClient.swift:517
Decoding server errors
// [REQ:gemSpec_IDP_Frontend:A_19937,A_20605] Decoding server errors
private static func responseError(for body: Data) -> IDPError {
../Sources/eRpApp/Common/ErrorLocalization/IDPError+Localization.swift:23
Localized description of server errors
// [REQ:gemSpec_IDP_Frontend:A_19937,A_20605,A_20085] Localized description of server errors
case let .internal(error: error): return error.localizedDescription
A_20606
../Sources/HTTPClient/DefaultHTTPClient.swift:39
Live URLs not present in NSAppTransportSecurity exception list for allowed
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
A_20607
no exceptions set in NSAppTransportSecurity, HTTP via TLS is enforced; OS will use system root certificates in combination with set pinned certificates. see also: Requirements for Connecting Using ATS
A_20608
: Implemented with ATS within Info.plist
, path: NSAppTransportSecurity.NSPinnedDomains
A_20608-01
: Implemented with ATS within Info.plist
, path: NSAppTransportSecurity.NSPinnedDomains
A_20609
no exceptions set in NSAppTransportSecurity, HTTP via TLS is enforced; OS will use system root certificates in combination with set pinned certificates. see also: Requirements for Connecting Using ATS
: Implemented with ATS within Info.plist
, path: NSAppTransportSecurity.NSPinnedDomains
A_20614
../Sources/IDP/DefaultIDPSession.swift:265
// [REQ:gemSpec_IDP_Frontend:A_20617-01,A_20623,A_20614]
.validateOrNil(with: trustStoreSession, timeProvider: time)
A_20617-01
../Sources/IDP/DefaultIDPSession.swift:265
// [REQ:gemSpec_IDP_Frontend:A_20617-01,A_20623,A_20614]
.validateOrNil(with: trustStoreSession, timeProvider: time)
../Sources/IDP/DefaultIDPSession.swift:298
// [REQ:gemSpec_IDP_Frontend:A_20617-01,A_20623]
.validate(with: self.trustStoreSession, timeProvider: self.time)
../Sources/IDP/DefaultIDPSession.swift:714
/// Returns a Publisher that validates the input streams discoveryDocument against the given trustStoreSession. If
/// the validity cannot be verified, the publisher fails with an `IDPError.trustStore` error.
///
/// [REQ:gemSpec_IDP_Frontend:A_20617-01]
/// [REQ:gemSpec_IDP_Frontend:A_20623]
///
/// - Parameter trustStoreSession: `TrustStoreSession` that is used to check the validity and trust of the
/// discoveryDocument.
/// - Returns: An AnyPublisher of `DiscoveryDocument`and `IDPError`
func validate(with trustStoreSession: TrustStoreSession,
../Sources/IDP/DefaultIDPSession.swift:746
/// Returns a Publisher that validates the input streams discoveryDocument and returns nil if validity cannot be
/// checked. All Errors are caught and result in an empty discoveryDocument.
///
/// [REQ:gemSpec_IDP_Frontend:A_20617-01,A_20623]
///
/// - Parameters:
/// - trustStoreSession: `TrustStoreSession` that is used to check the validity and trust of the disoveryDocument.
/// - time: Time provider to check the discovery document against.
/// - Returns: An AnyPublisher of `DiscoveryDocument`and `Never`.
func validateOrNil(with trustStoreSession: TrustStoreSession,
A_20618
no exceptions set in NSAppTransportSecurity, HTTP via TLS is enforced; OS will use system root certificates in combination with set pinned certificates. see also: Requirements for Connecting Using ATS
: Implemented with ATS within Info.plist
, path: NSAppTransportSecurity.NSPinnedDomains
A_20623
../Sources/TrustStore/X509TrustStore.swift:193
IDP oid
case oidErpVau // 1.2.276.0.76.4.258 == 0x06082A8214004C048202
// [REQ:gemSpec_IDP_Frontend:A_20623] IDP oid
case oidIdpd // 1.2.276.0.76.4.260 == 0x06082A8214004C048204
}
../Sources/IDP/DefaultIDPSession.swift:298
// [REQ:gemSpec_IDP_Frontend:A_20617-01,A_20623]
.validate(with: self.trustStoreSession, timeProvider: self.time)
../Sources/IDP/DefaultIDPSession.swift:715
/// Returns a Publisher that validates the input streams discoveryDocument against the given trustStoreSession. If
/// the validity cannot be verified, the publisher fails with an `IDPError.trustStore` error.
///
/// [REQ:gemSpec_IDP_Frontend:A_20617-01]
/// [REQ:gemSpec_IDP_Frontend:A_20623]
///
/// - Parameter trustStoreSession: `TrustStoreSession` that is used to check the validity and trust of the
/// discoveryDocument.
/// - Returns: An AnyPublisher of `DiscoveryDocument`and `IDPError`
func validate(with trustStoreSession: TrustStoreSession,
../Sources/IDP/DefaultIDPSession.swift:718
Validation call
// [REQ:gemSpec_IDP_Frontend:A_20623] Validation call
return trustStoreSession.validate(discoveryDocument: document)
../Sources/IDP/DefaultIDPSession.swift:746
/// Returns a Publisher that validates the input streams discoveryDocument and returns nil if validity cannot be
/// checked. All Errors are caught and result in an empty discoveryDocument.
///
/// [REQ:gemSpec_IDP_Frontend:A_20617-01,A_20623]
///
/// - Parameters:
/// - trustStoreSession: `TrustStoreSession` that is used to check the validity and trust of the disoveryDocument.
/// - time: Time provider to check the discovery document against.
/// - Returns: An AnyPublisher of `DiscoveryDocument`and `Never`.
func validateOrNil(with trustStoreSession: TrustStoreSession,
../Sources/IDP/DefaultIDPSession.swift:776
Validation
/// Returns a publisher that checks a discoveryDocument against the trust store. The Stream contains an output
/// boolean for the plain check or an TrustStoreError in case the TrustStoreSession sub streams failed.
///
/// [REQ:gemSpec_IDP_Frontend:A_20623] Validation
///
/// - Parameter discoveryDocument: The DiscoveryDocument that needs to be checked.
/// - Returns: A publisher that contains an output with the check value or an failure if the check failed
/// due to an underlying error.
func validate(discoveryDocument: DiscoveryDocument) -> AnyPublisher<Bool, TrustStoreError> {
../Sources/TrustStore/X509TrustStore.swift:179
oid check
} else if eeCert.contains(oidBytes: .oidIdpd) { // [REQ:gemSpec_IDP_Frontend:A_20623] oid check
return (vauCerts, idpCerts + [eeCert])
../Sources/IDP/DefaultIDPSession.swift:265
// [REQ:gemSpec_IDP_Frontend:A_20617-01,A_20623,A_20614]
.validateOrNil(with: trustStoreSession, timeProvider: time)
A_20625
../Sources/IDP/DefaultIDPSession.swift:210
Validate ID_TOKEN signature
// [REQ:gemSpec_IDP_Frontend:A_20625] Validate ID_TOKEN signature
guard let jwt = try? JWT(from: decrypted.idToken),
A_20700-05
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:25
sign with C.CH.AUT
// [REQ:gemSpec_IDP_Frontend:A_20700-05,A_20700-07] sign with C.CH.AUT
return challengeSession.sign(
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:435
sign with C.CH.AUT
// [REQ:gemSpec_IDP_Frontend:A_20700-05,A_20700-07] sign with C.CH.AUT
return session.sign(
A_20700-07
../Sources/IDP/DefaultSecureEnclaveSignatureProvider.swift:127
Biometrics only, other modes currently not supported
// [REQ:gemSpec_IDP_Frontend:A_20700-07] Biometrics only, other modes currently not supported
amr: [
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:25
sign with C.CH.AUT
// [REQ:gemSpec_IDP_Frontend:A_20700-05,A_20700-07] sign with C.CH.AUT
return challengeSession.sign(
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:139
C.CH.AUT
// [REQ:gemSpec_IDP_Frontend:A_20700-07] C.CH.AUT
// [REQ:gemF_Tokenverschlüsselung:A_20526-01] Smartcard signature
// [REQ:gemF_Tokenverschlüsselung:A_20700-05,A_20700-06] sign
.sign(challengeSession: challengeSession)
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:425
C.CH.AUT
// [REQ:gemSpec_IDP_Frontend:A_20700-07] C.CH.AUT
// [REQ:gemF_Tokenverschlüsselung:A_20526-01] Smartcard signature
// [REQ:gemF_Tokenverschlüsselung:A_20700-06] sign
.readAutCertificate()
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:435
sign with C.CH.AUT
// [REQ:gemSpec_IDP_Frontend:A_20700-05,A_20700-07] sign with C.CH.AUT
return session.sign(
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:466
perform signature with OpenHealthCardKit
// [REQ:gemSpec_IDP_Frontend:A_20700-07] perform signature with OpenHealthCardKit
card.sign(data: message)
A_20740
../Sources/IDP/internal/RealIDPClient.swift:110
transfer
// [REQ:gemSpec_IDP_Frontend:A_20740] transfer
name: "redirect_uri",
../Sources/IDP/internal/RealIDPClient.swift:246
transfer
// [REQ:gemSpec_IDP_Frontend:A_20740] transfer
"redirect_uri": redirectURI ?? clientConfig.redirectURI.absoluteString,
../Sources/eRpApp/AppConfiguration.swift:61
Actual redirect uri
// [REQ:gemSpec_IDP_Frontend:A_20740] Actual redirect uri
let redirectUri = URL(string: "https://redirect.gematik.de/erezept")! // swiftlint:disable:this force_unwrapping
let extAuthRedirectUri = URL(
A_20741
Configuration within app-configuration.json
, organizational process as in A_20603
A_21322
../Sources/eRpApp/Session/KeychainStorage.swift:20
Storage implementation uses iOS Keychain
// [REQ:gemSpec_eRp_FdV:A_19186]
// [REQ:gemSpec_eRp_FdV:A_19188] Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_IDP_Frontend:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemF_Tokenverschlüsselung:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Implementation
// [REQ:BSI-eRp-ePA:O.Purp_8#1,O.Arch_2#5,O.Arch_4#3,O.Source_7#2,O.Data_2#2] Implementation of data storage that is
// persisted via keychain
class KeychainStorage: SecureUserDataStore, IDPStorage, SecureEGKCertificateStorage {
A_21323
../Sources/IDP/DefaultIDPSession.swift:185
Crypto box contains Token-Key
// [REQ:gemSpec_IDP_Frontend:A_20529-01] Encryption
// [REQ:gemSpec_IDP_Frontend:A_21323,A_21324#1] Crypto box contains `Token-Key`
guard let encryptedKeyVerifier = try? KeyVerifier(
../Sources/IDP/internal/TokenPayload.swift:169
Encode into JSON object
// [REQ:gemSpec_IDP_Frontend:A_21323#2] Encode into JSON object
// [REQ:gemSpec_IDP_Frontend:A_21324#3] Encode into JSON object
guard let keyVerifierEncoded = try? KeyVerifier.jsonEncoder.encode(self) else {
A_21324
../Sources/IDP/DefaultIDPSession.swift:185
Crypto box contains Token-Key
// [REQ:gemSpec_IDP_Frontend:A_20529-01] Encryption
// [REQ:gemSpec_IDP_Frontend:A_21323,A_21324#1] Crypto box contains `Token-Key`
guard let encryptedKeyVerifier = try? KeyVerifier(
../Sources/IDP/internal/TokenPayload.swift:170
Encode into JSON object
// [REQ:gemSpec_IDP_Frontend:A_21323#2] Encode into JSON object
// [REQ:gemSpec_IDP_Frontend:A_21324#3] Encode into JSON object
guard let keyVerifierEncoded = try? KeyVerifier.jsonEncoder.encode(self) else {
A_21325
../Sources/IDP/IDPInterceptor.swift:56
// [REQ:gemSpec_IDP_Frontend:A_20602,A_21325#1]
request.setValue("\(token.tokenType) \(token.accessToken)", forHTTPHeaderField: "Authorization")
../Sources/eRpApp/Session/StandardSessionContainer.swift:346
Interceptor order defines what is encrypted via VAU
// [REQ:gemSpec_IDP_Frontend:A_21325#2] Interceptor order defines what is encrypted via VAU
let interceptors: [Interceptor] = [
A_21414
../Sources/IDP/DefaultIDPSession.swift:322
Encrypt ACCESS_TOKEN when requesting the pairing endpoint
// [REQ:gemSpec_IDP_Frontend:A_21414] Encrypt ACCESS_TOKEN when requesting the pairing endpoint
guard let tokenJWT = try? JWT(from: token.accessToken),
A_21416
../Sources/IDP/DefaultIDPSession.swift:316
Encryption
/// [REQ:gemSpec_IDP_Frontend:A_21416] Encryption
guard let jwe = try? registrationData.encrypted(with: document.encryptionPublicKey,
../Sources/IDP/Models/RegistrationData.swift:14
Data Structure
/// Bundles data needed for creating and verifiying a pairing.
/// [REQ:gemF_Biometrie:A_21415:Registration_Data]
/// [REQ:gemSpec_IDP_Frontend:A_21416] Data Structure
public struct RegistrationData: Claims, Codable {
../Sources/IDP/Models/RegistrationData.swift:105
Encryption
/// [REQ:gemF_Biometrie:A_21415:Encrypted_Registration_Data] Returns JWE encrypted Registration_Data
/// [REQ:gemSpec_IDP_Frontend:A_21416] Encryption
func encrypted(with publicKey: BrainpoolP256r1.KeyExchange.PublicKey,
A_21431
../Sources/IDP/DefaultIDPSession.swift:396
Encryption
/// [REQ:gemSpec_IDP_Frontend:A_21431] Encryption
guard let jwe = try? signedChallenge.encrypted(with: document.encryptionPublicKey,
../Sources/IDP/Models/SignedAuthenticationData.swift:35
exp header
/// [REQ:gemSpec_IDP_Frontend:A_21431] exp header
expiry: originalChallenge.challenge.exp,
A_21443
../Sources/IDP/DefaultIDPSession.swift:347
Encrypt ACCESS_TOKEN when requesting the unregister endpoint
// [REQ:gemSpec_IDP_Frontend:A_21443] Encrypt ACCESS_TOKEN when requesting the unregister endpoint
guard let tokenJWT = try? JWT(from: token.accessToken),
../Sources/IDP/DefaultIDPSession.swift:371
Encrypt ACCESS_TOKEN when requesting the list endpoint
// [REQ:gemSpec_IDP_Frontend:A_21443] Encrypt ACCESS_TOKEN when requesting the list endpoint
guard let tokenJWT = try? JWT(from: token.accessToken),
A_21574
../Sources/eRpApp/Screens/CardWall/Login/CardWallLoginOptionDomain.swift:113
Present user information
// [REQ:gemSpec_IDP_Frontend:A_21574] Present user information
return EffectTask.send(.presentSecurityWarning)
../Sources/eRpApp/Screens/CardWall/Login/CardWallLoginOptionView.swift:117
Actual view
// [REQ:gemSpec_IDP_Frontend:A_21574] Actual view
// [REQ:BSI-eRp-ePA:O.Resi_1#3] View containing information regarding the login options.
struct PrivacyWarningViewContainer: View {
A_21576
../Sources/IDP/DefaultIDPSession.swift:340
deletion call
// [REQ:gemSpec_IDP_Frontend:A_21576] deletion call
public func unregisterDevice(_ keyIdentifier: String, token: IDPToken) -> AnyPublisher<Bool, IDPError> {
A_21578
iOS only allows Biometric access via secure enclave or higher order apis.
../Sources/IDP/PrivateKeyContainer.swift:144
Enforced via access attribute
// [REQ:gemSpec_IDP_Frontend:A_21578,A_21579,A_21580,A_21583] Enforced via access attribute
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
A_21579
../Sources/IDP/PrivateKeyContainer.swift:144
Enforced via access attribute
// [REQ:gemSpec_IDP_Frontend:A_21578,A_21579,A_21580,A_21583] Enforced via access attribute
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
A_21580
../Sources/IDP/PrivateKeyContainer.swift:144
Enforced via access attribute
// [REQ:gemSpec_IDP_Frontend:A_21578,A_21579,A_21580,A_21583] Enforced via access attribute
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
A_21581
../Sources/IDP/PrivateKeyContainer.swift:140
Algorithm selection
// [REQ:gemSpec_IDP_Frontend:A_21581,A_21589] Algorithm selection
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
A_21582
../Sources/IDP/PrivateKeyContainer.swift:128
method selection
// [REQ:gemSpec_IDP_Frontend:A_21582] method selection
// [REQ:gemSpec_IDP_Frontend:A_21587] via `.privateKeyUsage`
[.privateKeyUsage,
A_21583
Secure Enclave is enforced with code attributes.
../Sources/IDP/PrivateKeyContainer.swift:144
Enforced via access attribute
// [REQ:gemSpec_IDP_Frontend:A_21578,A_21579,A_21580,A_21583] Enforced via access attribute
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
A_21584
There is no API to allow or disallow an biometric authentication, iOS is handling the authorization process while using the private key for any cryptographic operation.
../Sources/IDP/PrivateKeyContainer.swift:235
private key usage triggers biometric unlock
// [REQ:gemSpec_IDP_Frontend:A_21584] private key usage triggers biometric unlock
guard let signature = SecKeyCreateSignature(privateKey,
A_21585
Default behavior for all apps when using private access group (https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps)
A_21586
iOS will delete all user related data after user-account reset. Key-Chain data is not being synced with iCloud since kSecAttrSynchronizable
is not applied
../Sources/IDP/PrivateKeyContainer.swift:125
prevents migration to other devices
// [REQ:gemSpec_IDP_Frontend:A_21586] prevents migration to other devices
// [REQ:BSI-eRp-ePA:O.Data_15#2] prevents migration to other devices
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
../Sources/IDP/PrivateKeyContainer.swift:131
invalidates biometry after changes
// [REQ:gemSpec_IDP_Frontend:A_21586] invalidates biometry after changes
// [REQ:BSI-eRp-ePA:O.Biom_6#2] invalidates biometry after changes
.biometryCurrentSet], &error) else {
A_21587
../Sources/IDP/PrivateKeyContainer.swift:129
via .privateKeyUsage
// [REQ:gemSpec_IDP_Frontend:A_21582] method selection
// [REQ:gemSpec_IDP_Frontend:A_21587] via `.privateKeyUsage`
[.privateKeyUsage,
A_21588
../Sources/IDP/DefaultSecureEnclaveSignatureProvider.swift:13
key identfier generator, number of bytes 32
// [REQ:gemSpec_IDP_Frontend:A_21588] key identfier generator, number of bytes 32
keyIdentifierGenerator: @escaping (() throws -> Data) = { try generateSecureRandom(length: 32) },
../Sources/IDP/DefaultSecureEnclaveSignatureProvider.swift:36
Key generation
// [REQ:gemSpec_IDP_Frontend:A_21588] Key generation
let keyIdentifier = try keyIdentifierGenerator()
../Sources/IDP/DefaultSecureEnclaveSignatureProvider.swift:101
usage as base64 encoded string
// [REQ:gemSpec_IDP_Frontend:A_21588] usage as base64 encoded string
guard let someIdentifier = identifier,
A_21589
../Sources/IDP/PrivateKeyContainer.swift:140
Algorithm selection
// [REQ:gemSpec_IDP_Frontend:A_21581,A_21589] Algorithm selection
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
../Sources/IDP/PrivateKeyContainer.swift:142
Key length
// [REQ:gemSpec_IDP_Frontend:A_21589] Key length
kSecAttrKeySizeInBits as String: 256,
A_21590
References of SecureEnclaveSignatureProvider
is limited to registration and altVerify usage.
../Sources/IDP/PrivateKeyContainer.swift:18
This is the container to represent biometric keys. Usage is limited to
/// Represents a (SecureEnclave) private key, namely `PrK_SE_AUT`, secured by iOS Biometrics.
///
/// [REQ:gemSpec_IDP_Frontend:A_21590] This is the container to represent biometric keys. Usage is limited to
/// authorization purposes
/// [REQ:BSI-eRp-ePA:O.Cryp_7#2] Container for private key operations using secure enclave private keys
public struct PrivateKeyContainer {
A_21591
../Sources/IDP/Models/RegistrationData.swift:57
/// [REQ:gemF_Biometrie:A_21415:Device_Type]
/// [REQ:gemSpec_IDP_Frontend:A_21591]
public struct DeviceType: Codable {
../Sources/IDP/UIDevice+Extension.swift:10
/// [REQ:gemSpec_IDP_Frontend:A_21591,A_21600]
func deviceInformation() -> RegistrationData.DeviceInformation {
../Sources/IDP/UIDevice+Extension.swift:43
/// [REQ:gemSpec_IDP_Frontend:A_21591,A_21600]
func deviceInformation() -> RegistrationData.DeviceInformation {
A_21595
../Sources/eRpApp/Session/KeychainStorage.swift:233
Store within keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Store within keychain
success = try keychainHelper.setGenericPassword(keyIdentifier, for: idpBiometricKeyIdentifier)
../Sources/IDP/DefaultSecureEnclaveSignatureProvider.swift:51
Store pairing data
// [REQ:gemSpec_IDP_Frontend:A_21598,A_21595,A_21595] Store pairing data
certificateStorage.set(certificate: certificate)
../Sources/IDP/DefaultSecureEnclaveSignatureProvider.swift:72
case deletion
// [REQ:gemSpec_IDP_Frontend:A_21595] case deletion
certificateStorage.set(certificate: nil)
../Sources/IDP/IDPStorage.swift:12
Storage Protocol
/// Interface to access an eGK Certificate that should be kept private
/// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Protocol
public protocol SecureEGKCertificateStorage {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:208
Failure will delete paring data
// [REQ:gemSpec_IDP_Frontend:A_21598,A_21595] Failure will delete paring data
// [REQ:BSI-eRp-ePA:O.Source_5#5] Failure will delete paring data
_ = try? sessionProvider.signatureProvider(for: profileID).abort(pairingSession: pairingSession)
../Sources/eRpApp/Session/KeychainStorage.swift:22
Storage Implementation
// [REQ:gemSpec_eRp_FdV:A_19186]
// [REQ:gemSpec_eRp_FdV:A_19188] Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_IDP_Frontend:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemF_Tokenverschlüsselung:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Implementation
// [REQ:BSI-eRp-ePA:O.Purp_8#1,O.Arch_2#5,O.Arch_4#3,O.Source_7#2,O.Data_2#2] Implementation of data storage that is
// persisted via keychain
class KeychainStorage: SecureUserDataStore, IDPStorage, SecureEGKCertificateStorage {
../Sources/eRpApp/Session/KeychainStorage.swift:206
Store within keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Store within keychain
_ = try? keychainHelper.setGenericPassword(derBytes, for: egkAuthCertIdentifier)
../Sources/IDP/DefaultSecureEnclaveSignatureProvider.swift:51
Store pairing data
// [REQ:gemSpec_IDP_Frontend:A_21598,A_21595,A_21595] Store pairing data
certificateStorage.set(certificate: certificate)
A_21598
../Sources/IDP/DefaultSecureEnclaveSignatureProvider.swift:51
Store pairing data
// [REQ:gemSpec_IDP_Frontend:A_21598,A_21595,A_21595] Store pairing data
certificateStorage.set(certificate: certificate)
../Sources/IDP/DefaultSecureEnclaveSignatureProvider.swift:69
Delete all stored keys/identifiers/certificate in case of an unsuccessful
// [REQ:gemSpec_IDP_Frontend:A_21598] Delete all stored keys/identifiers/certificate in case of an unsuccessful
// registration
public func abort(pairingSession: PairingSession) throws {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:208
Failure will delete paring data
// [REQ:gemSpec_IDP_Frontend:A_21598,A_21595] Failure will delete paring data
// [REQ:BSI-eRp-ePA:O.Source_5#5] Failure will delete paring data
_ = try? sessionProvider.signatureProvider(for: profileID).abort(pairingSession: pairingSession)
A_21600
../Sources/IDP/UIDevice+Extension.swift:10
/// [REQ:gemSpec_IDP_Frontend:A_21591,A_21600]
func deviceInformation() -> RegistrationData.DeviceInformation {
../Sources/IDP/UIDevice+Extension.swift:43
/// [REQ:gemSpec_IDP_Frontend:A_21591,A_21600]
func deviceInformation() -> RegistrationData.DeviceInformation {
A_21603
../Sources/eRpApp/Session/KeychainStorage.swift:252
Certificate
// [REQ:gemSpec_IDP_Frontend:A_21603] Certificate
set(certificate: nil)
../Sources/eRpApp/Session/ProfileSecureDataWiper.swift:41
Certificate
// [REQ:gemSpec_IDP_Frontend:A_20499,A_20499-01#2] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_eRp_FdV:A_20186] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_IDP_Frontend:A_21603] Certificate
storage.wipe()
../Sources/eRpApp/Session/ProfileSecureDataWiper.swift:49
key identifier
// [REQ:gemSpec_IDP_Frontend:A_21603] key identifier
storage.set(keyIdentifier: nil)
../Sources/eRpApp/Session/ProfileSecureDataWiper.swift:53
PrK_SE_AUT/PuK_SE_AUT
// If deletion fails we cannot do anything
// [REQ:gemSpec_IDP_Frontend:A_21603] PrK_SE_AUT/PuK_SE_AUT
_ = try? PrivateKeyContainer.deleteExistingKey(for: identifier)
gemSpec_IDP_Sek
A_22294
../Sources/eRpApp/Screens/CardWall/ExtAuth/CardWallExtAuthConfirmationDomain.swift:81
Start login with KK
// [REQ:gemSpec_IDP_Sek:A_22294] Start login with KK
// [REQ:BSI-eRp-ePA:O.Auth_3#2,O.Plat_10#2] Start login with KK
return .publisher(
../Sources/eRpApp/Screens/CardWall/ExtAuth/CardWallExtAuthSelectionDomain.swift:112
Select KK
// [REQ:gemSpec_IDP_Sek:A_22294] Select KK
state.selectedKK = entry
A_22295
../Sources/IDP/DefaultIDPSession.swift:447
Usage of kk_app_id
// [REQ:gemSpec_IDP_Sek:A_22295] Usage of kk_app_id
let extAuth = IDPExtAuth(kkAppId: entry.identifier,
A_22296
../Sources/IDP/DefaultIDPSession.swift:419
Signature verification
// [REQ:gemSpec_IDP_Sek:A_22296] Signature verification
// [REQ:BSI-eRp-ePA:O.Resi_6#3] Discovery Document signature verification
guard try jwtContainer.verify(with: document.discKey) == true else {
../Sources/eRpApp/Screens/CardWall/ExtAuth/CardWallExtAuthSelectionDomain.swift:95
Load available apps
// [REQ:gemSpec_IDP_Sek:A_22296] Load available apps
return .publisher(
A_22299
../Sources/IDP/DefaultIDPSession.swift:465
Remember State parameter for later verification
// [REQ:gemSpec_IDP_Sek:A_22299] Remember State parameter for later verification
guard let components = URLComponents(url: redirectUrl, resolvingAgainstBaseURL: true),
../Sources/eRpApp/Screens/CardWall/ExtAuth/CardWallExtAuthConfirmationDomain.swift:96
Follow redirect
// [REQ:gemSpec_IDP_Sek:A_22299] Follow redirect
// [REQ:BSI-eRp-ePA:O.Plat_10#3] Follow redirect
guard environment.resourceHandler.canOpenURL(url) else {
A_22301
../Sources/IDP/DefaultIDPSession.swift:507
Send authorization request
// [REQ:gemSpec_IDP_Sek:A_22301] Send authorization request
return extAuthVerify(verify)
../Sources/IDP/DefaultIDPSession.swift:524
Match request with existing state
// [REQ:gemSpec_IDP_Sek:A_22301] Match request with existing state
guard let fastTrackChallengeSession = extAuthRequestStorage.getExtAuthRequest(for: state) else {
A_22313
../Sources/eRpApp/Screens/CardWall/ExtAuth/CardWallExtAuthConfirmationDomain.swift:102
Remember State parameter for later verification
// [REQ:gemSpec_IDP_Sek:A_22313] Remember State parameter for later verification
environment.resourceHandler.open(url, options: [:]) { result in
gemSpec_Krypt
A_17124
no exceptions set in NSAppTransportSecurity, see also: Requirements for Connecting Using ATS
A_17205
The app does not use the TSL. All TSL related parts are handled within the eRp-FD.
A_17207
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:481
Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
var alg: JWT.Algorithm? {
../Sources/IDP/DefaultIDPSession.swift:287
Only implemented for brainpoolP256r1
// Validate JWT/DiscoveryDocument signature
// [REQ:gemSpec_Krypt:A_17207] Only implemented for brainpoolP256r1
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02] Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4361-02] Assure that brainpoolP256r1 is used
// [REQ:BSI-eRp-ePA:O.Resi_6#4] Discovery Document signature verification
guard (try? fetchedDocument.backing.verify(with: fetchedDocument.discKey)) ?? false else {
../Sources/IDP/DefaultIDPSession.swift:651
Only implemented for brainpoolP256r1
// [REQ:gemSpec_Krypt:A_17207] Only implemented for brainpoolP256r1
// [REQ:gemSpec_IDP_Frontend:A_19908-01] Signature check
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let verified = try? challenge.challenge.verify(with: document.authentication.cert),
../Sources/IDP/internal/JWT/JWTSignatureVerifier.swift:23
// [REQ:gemSpec_Krypt:A_17207]
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02]
public func verify(signature raw: Data, message: Data) throws -> Bool {
../Sources/IDP/internal/JWT/JWTSignatureVerifier.swift:33
// [REQ:gemSpec_Krypt:A_17207]
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let key = brainpoolP256r1VerifyPublicKey() else {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:19
Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let alg = certificate.info.algorithm.alg else {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:45
Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard certificate.info.algorithm.alg == .bp256r1 else {
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:429
Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let alg = certificate.info.algorithm.alg else {
brainpoolP256r1 is used for creating/verifying key for signature usage; exception: Biometric use case uses secp256r1 (is considered in respective specification)
A_17322
no exceptions set in NSAppTransportSecurity, see also: Requirements for Connecting Using ATS
A_17359
brainpoolP256r1 is used for creating/verifying key for signature usage; exception: Biometric use case uses secp256r1 (is considered in respective specification)
A_17775
We cannot interfere with cipher suite lists, see Requirements for Connecting Using ATS for actual order.
A_18464
../Sources/HTTPClient/DefaultHTTPClient.swift:36
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
A_18467
../Sources/HTTPClient/DefaultHTTPClient.swift:36
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
A_19215
We use https only, see DefaultHTTPClient.swift
. ATS forbids other connections.
A_20161-01
../Sources/VAUClient/VAUInterceptor.swift:47
// Prepare outer request (encrypt original request and embed it into a new one)
// [REQ:gemSpec_Krypt:A_20161-01]
.processToVauRequest(urlRequest: request, vauCryptoProvider: vauCryptoProvider)
../Sources/VAUClient/VAUInterceptor.swift:63
// Prepare outer request (encrypt original request and embed it into a new one)
// [REQ:gemSpec_Krypt:A_20161-01]
func processToVauRequest(
../Sources/VAUClient/internal/VAUCrypto.swift:26
/// Perform encryption of the data that the implementing instance has been initialized with
/// in order to send it to a VAU service endpoint. See: gemSpec_Krypt A_20161-01
///
/// [REQ:gemSpec_Krypt:A_20161-01]
///
/// - Returns: Encrypted HTTPRequest as specified to be sent to a VAU endpoint
/// - Throws: `VAUError` in case of encryption failure
func encrypt() throws -> Data
5 ../Sources/VAUClient/internal/VAUCrypto.swift:88
// [REQ:gemSpec_Krypt:A_20161-01:5]
guard let payload = "1 \(bearerToken) \(requestId) \(symKeyHex) \(message)".data(using: .utf8) else {
6a-g ../Sources/VAUClient/internal/VAUCrypto.swift:135
/// Perform Elliptic Curve Integrated Encryption Scheme [SEC1-2009] on some payload
/// [REQ:gemSpec_Krypt:A_20161-01:6a-g]
static func encrypt(
A_20163
../Sources/VAUClient/internal/VAUCrypto.swift:34
/// Perform decryption and validation of given data with the secret key material the implementing instance holds.
///
/// [REQ:gemSpec_Krypt:A_20163]
///
/// - Parameter data: Data to be decrypted
/// - Returns: Decrypted UTF8 string representation of the given data
/// - Throws: `VAUError` in case of decryption failure or when the decrypted data could not be validated
func decrypt(data: Data) throws -> String
A_20174
../Sources/VAUClient/VAUInterceptor.swift:52
// Process VAU server response (validate and extract+decrypt inner FHIR service response)
// [REQ:gemSpec_Krypt:A_20174]
.handleUserPseudonym(vauEndpointHandler: self.vauEndpointHandler)
../Sources/VAUClient/VAUInterceptor.swift:130
// Process VAU server response (validate and extract+decrypt inner FHIR service response)
// [REQ:gemSpec_Krypt:A_20174]
static func processVauResponse(httpResponse: HTTPResponse, vauCrypto: VAUCrypto, originalUrl: URL) throws
3 ../Sources/VAUClient/internal/VAUCrypto.swift:112
Decrypt using AES symmetric key
// Steps according to gemSpec_Krypt A_20174
// [REQ:gemSpec_Krypt:A_20174:3] Decrypt using AES symmetric key
guard let sealed = try? AES.GCM.SealedBox(combined: data),
4,5 ../Sources/VAUClient/internal/VAUCrypto.swift:119
Verify decrypted message. Expect: “1
// [REQ:gemSpec_Krypt:A_20174:4,5] Verify decrypted message. Expect: "1 <request id> <response header and body>"
let separated = utf8.split(separator: " ", maxSplits: 2).map { String($0) }
2 ../Sources/VAUClient/internal/VAUEndpointHandler.swift:13
// [REQ:gemSpec_Krypt:A_20174:2]
func didReceiveUserPseudonym(in httpResponse: HTTPResponse)
2 ../Sources/VAUClient/internal/VAUEndpointHandler.swift:34
// [REQ:gemSpec_Krypt:A_20174:2]
if let pseudonym = httpResponse.response.value(forHTTPHeaderField: "userpseudonym") {
A_20175
../Sources/VAUClient/VAUStorage.swift:15
/// Retrieve a previously saved UserPseudonym
///
/// [REQ:gemSpec_Krypt:A_20175]
var userPseudonym: AnyPublisher<String?, Never> { get }
../Sources/VAUClient/VAUStorage.swift:22
/// Set and save a user pseudonym
///
/// [REQ:gemSpec_Krypt:A_20175]
///
/// - Parameter userPseudonym: value to save. Pass in nil to unset
func set(userPseudonym: String?)
A_20607
We use https only, see DefaultHTTPClient.swift
. ATS forbids other connections.
A_21216
../Sources/TrustStore/internal/CertList.swift:11
/// Data structure according to */CertList* endpoint
/// [REQ:gemSpec_Krypt:A_21216]
public struct CertList: Codable, Equatable {
../Sources/VAUClient/internal/VAUCertList.swift:11
/// Data structure according to */CertList* endpoint
/// [REQ:gemSpec_Krypt:A_21216]
public struct VAUCertList: Codable, Equatable {
A_21217
../Sources/TrustStore/internal/OCSPList.swift:10
/// [REQ:gemSpec_Krypt:A_21217]
/// Data structure according to */OCSPList* endpoint
public struct OCSPList: Codable, Equatable {
A_21218
../Sources/eRpApp/AppConfiguration.swift:94
Gematik Root CA 3 as a trust anchor has to be set in the program code
// [REQ:gemSpec_Krypt:A_21218] Gematik Root CA 3 as a trust anchor has to be set in the program code
// swiftlint:disable:next force_try
let TRUSTANCHOR_GemRootCa3 = try! TrustAnchor(withPEM: """
../Sources/TrustStore/DefaultTrustStoreSession.swift:68
// [REQ:gemSpec_Krypt:A_21218,A_21222]
extension DefaultTrustStoreSession: TrustStoreSession {
../Sources/TrustStore/DefaultTrustStoreSession.swift:178
// [REQ:gemSpec_Krypt:A_21218]
func loadOCSPResponses() -> AnyPublisher<[OCSPResponse], TrustStoreError> {
../Sources/TrustStore/DefaultTrustStoreSession.swift:191
If only OCSP responses >12h available, we must request new ones
// [REQ:gemSpec_Krypt:A_21218] If only OCSP responses >12h available, we must request new ones
ocspResponses
../Sources/TrustStore/DefaultTrustStoreSession.swift:209
If only OCSP responses >12h available, …
// [REQ:gemSpec_Krypt:A_21218] If only OCSP responses >12h available, ...
ocspResponses
../Sources/TrustStore/DefaultTrustStoreSession.swift:233
If only OCSP responses >12h available, we must request new ones
// [REQ:gemSpec_Krypt:A_21218] If only OCSP responses >12h available, we must request new ones
func allSatisfyNotProducedBefore(date: Date) -> Bool {
../Sources/TrustStore/TrustStoreSession.swift:13
/// TrustStoreSession acts as an interactor/mediator for the TrustStoreClient and TrustStoreStorage
///
/// [REQ:gemSpec_Krypt:A_21218,A_21222]
public protocol TrustStoreSession {
../Sources/TrustStore/X509TrustStore.swift:13
// [REQ:gemSpec_Krypt:A_21218]
// [REQ:gemSpec_eRp_FdV:A_20032-01]
// Category A: Cross root certificates
private let rootCa: X509
../Sources/TrustStore/X509TrustStore.swift:105
/// Match a collection of `OCSPResponse`s with the end entity certificates of this `X509TrustStore`.
/// Checks response status, revocation status for each certificate and validates the signer certificates of
/// the responses itself.
///
/// [REQ:gemSpec_Krypt:A_21218]
/// [REQ:gemSpec_eRp_Fdv:A_20032-01]
///
/// - Note: This function assumes that up-to-dateness of the responses itself has already been checked.
///
/// - Returns: true on successful matching/validation, false if not successful or error
func checkEeCertificatesStatus(with ocspResponses: [OCSPResponse]) throws -> Bool {
../Sources/TrustStore/X509TrustStore.swift:103
OCSP responder certificates must be verifiable by the trust store
// [REQ:gemSpec_Krypt:A_21218] OCSP responder certificates must be verifiable by the trust store
let verifiedOCSPResponses = basicVerifyFilter(ocspResponses: ocspResponses)
../Sources/TrustStore/X509TrustStore.swift:111
For every EE certificate there must be a matching OCSP response
// [REQ:gemSpec_Krypt:A_21218] For every EE certificate there must be a matching OCSP response
let matchedResponses = try eeCertAndSignerTuple.map { eeCertificate, signer in
../Sources/TrustStore/X509TrustStore.swift:119
For every OCSP response there must be a matching EE certificate
// [REQ:gemSpec_Krypt:A_21218] For every OCSP response there must be a matching EE certificate
let matchedEeCerts = try ocspResponses.map { response in
../Sources/TrustStore/X509TrustStore.swift:130
OCSP responder certificates must be verifiable by the trust store
// [REQ:gemSpec_Krypt:A_21218] OCSP responder certificates must be verifiable by the trust store
private func basicVerifyFilter(ocspResponses: [OCSPResponse]) -> [OCSPResponse] {
(3) ../Sources/TrustStore/X509TrustStore.swift:151
Check ca_certs against category A certificates
// [REQ:gemSpec_Krypt:A_21218:(3)] Check ca_certs against category A certificates
private static let caCertRegex =
(4) ../Sources/TrustStore/X509TrustStore.swift:168
Check ee_certs against category A+B certificates
// [REQ:gemSpec_Krypt:A_21218:(4)] Check ee_certs against category A+B certificates
typealias VauAndIpdCerts = (vauCerts: [X509], idpCerts: [X509])
Note: grace period for OCSP responses is 12h
A_21222
../Sources/TrustStore/DefaultTrustStoreSession.swift:68
// [REQ:gemSpec_Krypt:A_21218,A_21222]
extension DefaultTrustStoreSession: TrustStoreSession {
../Sources/TrustStore/TrustStoreSession.swift:13
/// TrustStoreSession acts as an interactor/mediator for the TrustStoreClient and TrustStoreStorage
///
/// [REQ:gemSpec_Krypt:A_21218,A_21222]
public protocol TrustStoreSession {
A_21275
no exceptions set in NSAppTransportSecurity, see also: Requirements for Connecting Using ATS
A_21275−01
no exceptions set in NSAppTransportSecurity, see also: Requirements for Connecting Using ATS
A_21332
no exceptions set in NSAppTransportSecurity, see also: Requirements for Connecting Using ATS
A_4389
1 ../Sources/VAUClient/internal/VAUCrypto.swift:92
IVs must not be reused, IVs bit length must be larger or equal to 96
// [REQ:gemSpec_Krypt:A_4389:1] IVs must not be reused, IVs bit length must be larger or equal to 96
let nonceGenerator = { try VAURandom.generateSecureRandom(length: self.eciesSpec.ivSize) }
GS-A_4357
../Sources/IDP/internal/IDPCrypto.swift:15
Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
/// Key-pair generator type based on BrainpoolP256r1
///
/// [REQ:gemSpec_Krypt:GS-A_4357,GS-A_4367] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
public typealias BrainpoolKeyGenerator = () throws -> BrainpoolP256r1.KeyExchange.PrivateKey
../Sources/IDP/internal/JWT/JWE+KDF.swift:31
Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4357] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4367] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
case bpp256r1(BrainpoolP256r1.KeyExchange.PublicKey,
../Sources/VAUClient/internal/VAUCrypto.swift:97
Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4357] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4367] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:BSI-eRp-ePA:O.Cryp_3#5] Brainpool key generator
// [REQ:gemSpec_eRp_FdV:A_19179#5] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
let keyPairGenerator = { try BrainpoolP256r1.KeyExchange.generateKey() }
GS-A_4357-01
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:482
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
var alg: JWT.Algorithm? {
../Sources/IDP/DefaultIDPSession.swift:653
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Only implemented for brainpoolP256r1
// [REQ:gemSpec_IDP_Frontend:A_19908-01] Signature check
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let verified = try? challenge.challenge.verify(with: document.authentication.cert),
../Sources/IDP/internal/JWT/JWTSignatureVerifier.swift:24
// [REQ:gemSpec_Krypt:A_17207]
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02]
public func verify(signature raw: Data, message: Data) throws -> Bool {
../Sources/IDP/internal/JWT/JWTSignatureVerifier.swift:34
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207]
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let key = brainpoolP256r1VerifyPublicKey() else {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:20
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let alg = certificate.info.algorithm.alg else {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:46
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard certificate.info.algorithm.alg == .bp256r1 else {
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:430
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let alg = certificate.info.algorithm.alg else {
../Sources/IDP/DefaultIDPSession.swift:288
Assure that brainpoolP256r1 is used
// Validate JWT/DiscoveryDocument signature
// [REQ:gemSpec_Krypt:A_17207] Only implemented for brainpoolP256r1
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02] Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4361-02] Assure that brainpoolP256r1 is used
// [REQ:BSI-eRp-ePA:O.Resi_6#4] Discovery Document signature verification
guard (try? fetchedDocument.backing.verify(with: fetchedDocument.discKey)) ?? false else {
GS-A_4357-02
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:482
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
var alg: JWT.Algorithm? {
../Sources/IDP/DefaultIDPSession.swift:653
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Only implemented for brainpoolP256r1
// [REQ:gemSpec_IDP_Frontend:A_19908-01] Signature check
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let verified = try? challenge.challenge.verify(with: document.authentication.cert),
../Sources/IDP/internal/JWT/JWTSignatureVerifier.swift:24
// [REQ:gemSpec_Krypt:A_17207]
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02]
public func verify(signature raw: Data, message: Data) throws -> Bool {
../Sources/IDP/internal/JWT/JWTSignatureVerifier.swift:34
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207]
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let key = brainpoolP256r1VerifyPublicKey() else {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:20
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let alg = certificate.info.algorithm.alg else {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:46
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard certificate.info.algorithm.alg == .bp256r1 else {
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:430
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let alg = certificate.info.algorithm.alg else {
../Sources/IDP/DefaultIDPSession.swift:288
Assure that brainpoolP256r1 is used
// Validate JWT/DiscoveryDocument signature
// [REQ:gemSpec_Krypt:A_17207] Only implemented for brainpoolP256r1
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02] Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4361-02] Assure that brainpoolP256r1 is used
// [REQ:BSI-eRp-ePA:O.Resi_6#4] Discovery Document signature verification
guard (try? fetchedDocument.backing.verify(with: fetchedDocument.discKey)) ?? false else {
GS-A_4359
no exceptions set in NSAppTransportSecurity, see also: Requirements for Connecting Using ATS
GS-A_4361-02
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:482
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
var alg: JWT.Algorithm? {
../Sources/IDP/DefaultIDPSession.swift:653
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Only implemented for brainpoolP256r1
// [REQ:gemSpec_IDP_Frontend:A_19908-01] Signature check
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let verified = try? challenge.challenge.verify(with: document.authentication.cert),
../Sources/IDP/internal/JWT/JWTSignatureVerifier.swift:24
// [REQ:gemSpec_Krypt:A_17207]
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02]
public func verify(signature raw: Data, message: Data) throws -> Bool {
../Sources/IDP/internal/JWT/JWTSignatureVerifier.swift:34
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207]
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let key = brainpoolP256r1VerifyPublicKey() else {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:20
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let alg = certificate.info.algorithm.alg else {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.Environment+Biometrics.swift:46
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard certificate.info.algorithm.alg == .bp256r1 else {
../Sources/eRpApp/Screens/CardWall/ReadCard/NFCSignatureProvider.swift:430
Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:A_17207] Assure only brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02,GS-A_4361-02] Assure that brainpoolP256r1 is used
guard let alg = certificate.info.algorithm.alg else {
../Sources/IDP/DefaultIDPSession.swift:289
Assure that brainpoolP256r1 is used
// Validate JWT/DiscoveryDocument signature
// [REQ:gemSpec_Krypt:A_17207] Only implemented for brainpoolP256r1
// [REQ:gemSpec_Krypt:GS-A_4357-01,GS-A_4357-02] Assure that brainpoolP256r1 is used
// [REQ:gemSpec_Krypt:GS-A_4361-02] Assure that brainpoolP256r1 is used
// [REQ:BSI-eRp-ePA:O.Resi_6#4] Discovery Document signature verification
guard (try? fetchedDocument.backing.verify(with: fetchedDocument.discKey)) ?? false else {
GS-A_4367
../Sources/IDP/internal/IDPCrypto.swift:15
Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
/// Key-pair generator type based on BrainpoolP256r1
///
/// [REQ:gemSpec_Krypt:GS-A_4357,GS-A_4367] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
public typealias BrainpoolKeyGenerator = () throws -> BrainpoolP256r1.KeyExchange.PrivateKey
../Sources/IDP/internal/JWT/JWE+KDF.swift:32
Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4357] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4367] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
case bpp256r1(BrainpoolP256r1.KeyExchange.PublicKey,
../Sources/IDP/internal/SecKeyRandom.swift:20
/// Generate random Data with given length
///
/// [REQ:gemSpec_Krypt:GS-A_4367]
/// [REQ:BSI-eRp-ePA:O.Rand_1#2] Secure Random generator.
///
/// - Parameters:
/// - length: the number of bytes to generate
/// - randomizer: the randomizer to be used. Default: kSecRandomDefault
/// - Returns: the random initialized Data
/// - Throws: `IDPError`
public func generateSecureRandom(length: Int, randomizer: SecRandomRef? = kSecRandomDefault) throws -> Data {
../Sources/VAUClient/internal/VAUCrypto.swift:98
Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4357] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4367] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:BSI-eRp-ePA:O.Cryp_3#5] Brainpool key generator
// [REQ:gemSpec_eRp_FdV:A_19179#5] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
let keyPairGenerator = { try BrainpoolP256r1.KeyExchange.generateKey() }
../Sources/VAUClient/internal/VAURandom.swift:21
/// Generate random Data with given length
///
/// [REQ:gemSpec_Krypt:GS-A_4367]
/// [REQ:BSI-eRp-ePA:O.Rand_1#3] Secure Random generator.
///
/// - Parameters:
/// - length: the number of bytes to generate
/// - randomizer: the randomizer to be used. Default: kSecRandomDefault
/// - Returns: the random initialized Data
/// - Throws: `VAUError`
static func generateSecureRandom(length: Int, randomizer: SecRandomRef? = kSecRandomDefault) throws -> Data {
GS-A_4368
../Sources/IDP/internal/IDPCrypto.swift:55
AES key generation via CryptoKit
// [REQ:gemSpec_Krypt:GS-A_4389:1] 256bit GCM symmetric key
// [REQ:gemSpec_eRp_FdV:A_19179#3] AES key generation via CryptoKit
// [REQ:gemSpec_Krypt:GS-A_4368] AES key generation via CryptoKit
aesKey: SymmetricKey = SymmetricKey(size: SymmetricKeySize(bitCount: 256))
../Sources/VAUClient/internal/VAUCrypto.swift:149
AES key generation via CryptoKit
// [REQ:gemSpec_Krypt:GS-A_4389:2] 256bit GCM symmetric key
// [REQ:gemSpec_eRp_FdV:A_19179#6] AES key generation via CryptoKit
// [REQ:gemSpec_Krypt:GS-A_4368] AES key generation via CryptoKit
let secretKey = SymmetricKey(data: sharedSecret)
GS-A_4385
../Sources/HTTPClient/DefaultHTTPClient.swift:36
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
GS-A_4387
../Sources/HTTPClient/DefaultHTTPClient.swift:36
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
GS-A_4389
2 ../Sources/IDP/internal/IDPCrypto.swift:48
IVs must not be reused, IVs bit length must be larger or equal to 96
// [REQ:gemSpec_Krypt:GS-A_4389:2] IVs must not be reused, IVs bit length must be larger or equal to 96
try generateSecureRandom(length: IDPCrypto.AES256GCMSpec.nonceBytes)
1 ../Sources/IDP/internal/IDPCrypto.swift:53
256bit GCM symmetric key
// [REQ:gemSpec_Krypt:GS-A_4389:1] 256bit GCM symmetric key
// [REQ:gemSpec_eRp_FdV:A_19179#3] AES key generation via CryptoKit
// [REQ:gemSpec_Krypt:GS-A_4368] AES key generation via CryptoKit
aesKey: SymmetricKey = SymmetricKey(size: SymmetricKeySize(bitCount: 256))
2 ../Sources/VAUClient/internal/VAUCrypto.swift:147
256bit GCM symmetric key
// [REQ:gemSpec_Krypt:GS-A_4389:2] 256bit GCM symmetric key
// [REQ:gemSpec_eRp_FdV:A_19179#6] AES key generation via CryptoKit
// [REQ:gemSpec_Krypt:GS-A_4368] AES key generation via CryptoKit
let secretKey = SymmetricKey(data: sharedSecret)
1 ../Sources/VAUClient/internal/VAUCrypto.swift:166
256bit GCM symmetric key
// f) Encrypt
// [REQ:gemSpec_Krypt:GS-A_4389:1] 256bit GCM symmetric key
let sealedBox = try AES.GCM.seal(payload, using: cek, nonce: nonce)
GS-A_4390
../Sources/AVS/AVSCmsEncrypter.swift:20
RSAES-OAEP with MGF1 implemented in sub-framework OpenSSL-swift
// [REQ:gemSpec_Krypt:GS-A_4390] RSAES-OAEP with MGF1 implemented in sub-framework OpenSSL-swift
// [REQ:gemSpec_eRp_FdV:A_22778#3] Encryption of message to the Pharmacy is done with all provided certificates
// [REQ:gemSpec_eRp_FdV:A_22779#3] Encrypted message is of form of a PKCS#7 container (CMS)
// https://github.com/gematik/OpenSSL-Swift/blob/3c1ea91ba5abfefecfe3588815cb928d777e29ad/Sources/OpenSSL/CMS/CMSContentInfo.swift#L88
// swiftlint:disable:previous line_length
let cms = try CMSContentInfo.encryptPartial(data: data)
GS-A_5322
../Sources/HTTPClient/DefaultHTTPClient.swift:37
TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
GS−A_5035
We use https only, see DefaultHTTPClient.swift
. ATS forbids other connections.
GS−A_5322
There is no API to control the session length, developer forums suggest it is 10 minutes for iOS.
GS−A_5339
We cannot interfere with cipher suite lists, see Requirements for Connecting Using ATS for actual order.
GS−A_5526
We use the recommended NSURLSession for best network security Preventing insecure network connections
GS−A_5542
We use the recommended NSURLSession for best network security Preventing insecure network connections
gemSpec_eRp_APOVZD
A_21154
../Sources/eRpApp/Screens/Pharmacy/Search/PharmacySearchDomain.swift:283
If user defined filters contain location element, ask for permission
// [REQ:gemSpec_eRp_APOVZD:A_21154] If user defined filters contain location element, ask for permission
if filterOptions.contains(.currentLocation) {
A_21779
../Sources/Pharmacy/ModelsR4.Bundle+Location.swift:138
delivery pharmacy
// [[REQ:gemSpec_eRp_APOVZD:A_21779] delivery pharmacy
case "498":
../Sources/Pharmacy/ModelsR4.Bundle+Location.swift:195
Map pickup pharmacy
// [[REQ:gemSpec_eRp_APOVZD:A_21779] Map pickup pharmacy
case "OUTPHARM":
../Sources/Pharmacy/ModelsR4.Bundle+Location.swift:200
Map shipping pharmacy
// [[REQ:gemSpec_eRp_APOVZD:A_21779] Map shipping pharmacy
case "MOBL":
gemSpec_eRp_FdV
A_19086
Tracking is only implemented for the purpose of Usability-Tracking. Sessions are not persisted, session ids are recreated each app startup.
A_19087
Tracking is only implemented for the purpose of Usability-Tracking. Sessions are not persisted, session ids are recreated each app startup.
../Sources/eRpApp/Tracking/ContentSquareAnalyticsAdapter.swift:13
Implementation of the opt-in usability-tracker.
// [REQ:gemSpec_eRp_FdV:A_19087#2] Implementation of the opt-in usability-tracker.
final class ContentSquareAnalyticsAdapter: NSObject, Tracker {
A_19088
../Sources/eRpApp/Screens/Onboarding/OnboardingDomain.swift:188
Show comply route to display analytics usage within
// [REQ:gemSpec_eRp_FdV:A_19088,A_19091#1,A_19092] Show comply route to display analytics usage within
// onboarding
// [REQ:BSI-eRp-ePA:O.Purp_3#5] Show comply route to display analytics usage within onboarding
case .showTracking:
A_19089
../Sources/eRpApp/Screens/Settings/SettingsView.swift:257
User info for usage tracking
// [REQ:gemSpec_eRp_FdV:A_19089] User info for usage tracking
Label(title: { Text(L10n.stgTrkTxtExplanation) }, icon: {})
A_19090
../Sources/eRpApp/SceneDelegate.swift:275
activate after optIn is granted
// [REQ:gemSpec_eRp_FdV:A_19090] activate after optIn is granted
tracker.stopTracking()
../Sources/eRpApp/Screens/Onboarding/OnboardingDomain.swift:193
User confirms the opt in within settings
// [REQ:gemSpec_eRp_FdV:A_19090,A_19091#2] User confirms the opt in within settings
// [REQ:BSI-eRp-ePA:O.Purp_3#6] Accept usage analytics
case .alert(.presented(.allowTracking)):
../Sources/eRpApp/Screens/Settings/SettingsDomain.swift:209
User confirms the opt in within settings
// [REQ:gemSpec_eRp_FdV:A_19090,A_19091#4] User confirms the opt in within settings
// [REQ:BSI-eRp-ePA:O.Purp_5#4] User confirms the opt in within settings
case .confirmedOptInTracking:
../Sources/eRpApp/Tracking/ContentSquareAnalyticsAdapter.swift:42
activate after optIn is granted
// [REQ:gemSpec_eRp_FdV:A_19090] activate after optIn is granted
Contentsquare.start()
../Sources/eRpApp/Tracking/ContentSquareAnalyticsAdapter.swift:60
activate tracking only if permitted
/// Starts tracking and calls `optIn` if permission is granted by the user.
/// Calling this method after calling `resetSessionID()` will create a new `sessionID`
// [REQ:gemSpec_eRp_FdV:A_19090] activate tracking only if permitted
private func startIfPermitted() {
A_19091
../Sources/eRpApp/Screens/Onboarding/OnboardingDomain.swift:188
Show comply route to display analytics usage within
// [REQ:gemSpec_eRp_FdV:A_19088,A_19091#1,A_19092] Show comply route to display analytics usage within
// onboarding
// [REQ:BSI-eRp-ePA:O.Purp_3#5] Show comply route to display analytics usage within onboarding
case .showTracking:
../Sources/eRpApp/Screens/Onboarding/OnboardingDomain.swift:193
User confirms the opt in within settings
// [REQ:gemSpec_eRp_FdV:A_19090,A_19091#2] User confirms the opt in within settings
// [REQ:BSI-eRp-ePA:O.Purp_3#6] Accept usage analytics
case .alert(.presented(.allowTracking)):
../Sources/eRpApp/Screens/Settings/SettingsDomain.swift:200
Show comply route to display analytics usage within settings
// [REQ:gemSpec_eRp_FdV:A_19091#3] Show comply route to display analytics usage within settings
state.destination = .complyTracking
../Sources/eRpApp/Screens/Settings/SettingsDomain.swift:209
User confirms the opt in within settings
// [REQ:gemSpec_eRp_FdV:A_19090,A_19091#4] User confirms the opt in within settings
// [REQ:BSI-eRp-ePA:O.Purp_5#4] User confirms the opt in within settings
case .confirmedOptInTracking:
A_19092
../Sources/eRpApp/Screens/Onboarding/OnboardingDomain.swift:188
Show comply route to display analytics usage within
// [REQ:gemSpec_eRp_FdV:A_19088,A_19091#1,A_19092] Show comply route to display analytics usage within
// onboarding
// [REQ:BSI-eRp-ePA:O.Purp_3#5] Show comply route to display analytics usage within onboarding
case .showTracking:
../Sources/eRpApp/Screens/Onboarding/OnboardingDomain.swift:198
User may choose to not accept analytics
// [REQ:gemSpec_eRp_FdV:A_19092] User may choose to not accept analytics
// [REQ:BSI-eRp-ePA:O.Purp_3#7] Deny usage analytics
case .alert(.dismiss):
A_19093
Usage Tracking is called very sparse and boils down to one place where all visited screens are recorded. See usage of @Dependency(\.tracker)
for all cases where the actual analytics framework is used.
../Sources/eRpApp/Tracking/AnalyticsReducer.swift:38
Very sparse usage of actual tracking boils down to this call where
// [REQ:gemSpec_eRp_FdV:A_19093#2] Very sparse usage of actual tracking boils down to this call where
// only displayed screens are tracked
tracker.track(screen: newRoute)
A_19094
Usage Tracking is called very sparse and boils down to one place where all visited screens are recorded. See usage of @Dependency(\.tracker)
for all cases where the actual analytics framework is used.
A_19095
../Sources/eRpApp/Tracking/ContentSquareAnalyticsAdapter.swift:72
resets sessionID so that on next app start a new sessionID will be created
// [REQ:gemSpec_eRp_FdV:A_19095] resets sessionID so that on next app start a new sessionID will be created
Contentsquare.optOut()
A_19096
../Sources/eRpApp/Tracking/ContentSquareAnalyticsAdapter.swift:61
Remove existing sessions while starting the framework
// [REQ:gemSpec_eRp_FdV:A_19096] Remove existing sessions while starting the framework
Contentsquare.start()
A_19097
../Sources/eRpApp/Screens/Settings/SettingsView.swift:235
Toggle within Settings to enable and disable usage tracking
// [REQ:gemSpec_eRp_FdV:A_19097] Toggle within Settings to enable and disable usage tracking
// [REQ:BSI-eRp-ePA:O.Purp_5#1] Toggle within Settings to enable and disable usage tracking
// [REQ:BSI-eRp-ePA:O.Purp_6#1] Current Analytics state is inspectable by the user
// [REQ:gemSpec_eRp_FdV:A_19982#4] Opt out of analytics
Toggle(isOn: viewStore.binding(
A_19177
../Sources/eRpApp/Screens/Settings/Profiles/Protocol/AuditEventsView.swift:10
View displaying the audit events
// [REQ:gemSpec_eRp_FdV:A_19177#1,A_19185#2] View displaying the audit events
// [REQ:BSI-eRp-ePA:O.Auth_5#3] View displaying the audit events
struct AuditEventsView: View {
../Sources/eRpApp/Screens/Settings/Profiles/Edit/EditProfileView.swift:515
Actual Button to open the audit events
// [REQ:gemSpec_eRp_FdV:A_19177#2,A_19185#3] Actual Button to open the audit events
// [REQ:BSI-eRp-ePA:O.Auth_5#2] Actual Button to open the audit events
NavigationLinkStore(
A_19178
Is covered by our MSTG.
A_19179
Annotation in code
../Sources/IDP/internal/IDPCrypto.swift:44
Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_eRp_FdV:A_19179#2] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:BSI-eRp-ePA:O.Cryp_3#3] Brainpool key generator
try BrainpoolP256r1.KeyExchange.generateKey()
../Sources/IDP/internal/IDPCrypto.swift:54
AES key generation via CryptoKit
// [REQ:gemSpec_Krypt:GS-A_4389:1] 256bit GCM symmetric key
// [REQ:gemSpec_eRp_FdV:A_19179#3] AES key generation via CryptoKit
// [REQ:gemSpec_Krypt:GS-A_4368] AES key generation via CryptoKit
aesKey: SymmetricKey = SymmetricKey(size: SymmetricKeySize(bitCount: 256))
../Sources/IDP/internal/JWT/JWE+KDF.swift:36
Key pair generation delegated to OpenSSL
// [REQ:BSI-eRp-ePA:O.Cryp_3#4] Brainpool key generator
// [REQ:gemSpec_eRp_FdV:A_19179#4] Key pair generation delegated to OpenSSL
= { try BrainpoolP256r1.KeyExchange.generateKey() })
../Sources/VAUClient/internal/VAUCrypto.swift:100
Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4357] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:gemSpec_Krypt:GS-A_4367] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
// [REQ:BSI-eRp-ePA:O.Cryp_3#5] Brainpool key generator
// [REQ:gemSpec_eRp_FdV:A_19179#5] Key pair generation delegated to OpenSSL with BrainpoolP256r1 parameters
let keyPairGenerator = { try BrainpoolP256r1.KeyExchange.generateKey() }
../Sources/VAUClient/internal/VAUCrypto.swift:148
AES key generation via CryptoKit
// [REQ:gemSpec_Krypt:GS-A_4389:2] 256bit GCM symmetric key
// [REQ:gemSpec_eRp_FdV:A_19179#6] AES key generation via CryptoKit
// [REQ:gemSpec_Krypt:GS-A_4368] AES key generation via CryptoKit
let secretKey = SymmetricKey(data: sharedSecret)
A_19181-01
There is an opt-in option for analytics. Full app functionality is available also without opting in. The user’s choice is requested during onboarding and the user can opt-in and opt-out of analytics at any time. There are no further configuration choices. After successful authentication via health card the corresponding prescriptions, protocol data and messages are displayed.
A_19182
In order to minimize the risk of unknown vulnerabilities in dependencies, we use different measures: We develop according to Security by Design Principles (see E-Rezept-App - SSDLC.pdf - Section Richtlinien, Vorgaben und Best Practices). We train our engineers focussing on secure design and coding best practices (see Sicherheitsschulungen.pdf). We publish our Code on Github and use a bug bounty program (https://www.gematik.de/datensicherheit -> Coordinated Vulnerability Disclosure Program)
A_19183
We have no implementation of any sharing mechanism of FD data.
A_19184
../Sources/eRpApp/Screens/Onboarding/OnboardingAnalyticsView.swift:19
Information for the user what is collected
// [REQ:gemSpec_eRp_FdV:A_19184] Information for the user what is collected
VStack(alignment: .leading, spacing: 16) {
../Sources/eRpApp/Screens/Onboarding/OnboardingDomain.swift:206
Alert for the user to choose.
// [REQ:gemSpec_eRp_FdV:A_19184] Alert for the user to choose.
static let trackingAlertState: AlertState<Action.Alert> = {
A_19185
Communication with the Fachdienst is protocoled via Audit Events. The user can revise them in the Settings menu (Profile Settings).
../Sources/eRpApp/Screens/Settings/Profiles/Protocol/AuditEventsView.swift:10
View displaying the audit events
// [REQ:gemSpec_eRp_FdV:A_19177#1,A_19185#2] View displaying the audit events
// [REQ:BSI-eRp-ePA:O.Auth_5#3] View displaying the audit events
struct AuditEventsView: View {
../Sources/eRpApp/Screens/Settings/Profiles/Edit/EditProfileView.swift:515
Actual Button to open the audit events
// [REQ:gemSpec_eRp_FdV:A_19177#2,A_19185#3] Actual Button to open the audit events
// [REQ:BSI-eRp-ePA:O.Auth_5#2] Actual Button to open the audit events
NavigationLinkStore(
A_19186
../Sources/eRpApp/Session/KeychainStorage.swift:18
// [REQ:gemSpec_eRp_FdV:A_19186]
// [REQ:gemSpec_eRp_FdV:A_19188] Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_IDP_Frontend:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemF_Tokenverschlüsselung:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Implementation
// [REQ:BSI-eRp-ePA:O.Purp_8#1,O.Arch_2#5,O.Arch_4#3,O.Source_7#2,O.Data_2#2] Implementation of data storage that is
// persisted via keychain
class KeychainStorage: SecureUserDataStore, IDPStorage, SecureEGKCertificateStorage {
A_19187
../Sources/VAUClient/VAUInterceptor.swift:38
VAU Bearer must be set to trigger a request
// [REQ:gemSpec_eRp_FdV:A_19187] VAU Bearer must be set to trigger a request
return vauAccessTokenProvider.vauBearerToken
A_19188
session data (ACCESS_TOKEN, ID_TOKEN, SSO_TOKEN, CAN) is saved in the keychain. Its deletion is managed by the OS. The key chain is sandboxed and can only be shared with other apps by the same vendor when explicitly set. All other mentioned data is deleted.
../Sources/eRpApp/Session/KeychainStorage.swift:19
Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_eRp_FdV:A_19186]
// [REQ:gemSpec_eRp_FdV:A_19188] Deletion of data saved here is managed by the OS.
// [REQ:gemSpec_IDP_Frontend:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemF_Tokenverschlüsselung:A_21322] Storage implementation uses iOS Keychain
// [REQ:gemSpec_IDP_Frontend:A_21595] Storage Implementation
// [REQ:BSI-eRp-ePA:O.Purp_8#1,O.Arch_2#5,O.Arch_4#3,O.Source_7#2,O.Data_2#2] Implementation of data storage that is
// persisted via keychain
class KeychainStorage: SecureUserDataStore, IDPStorage, SecureEGKCertificateStorage {
A_19229
Audit events will be deleted when the referencing task is deleted. Cascading relationship “task -> audit event” is defined in Sources/eRpLocalStorage/Prescriptions/ErxTask.xcdatamodeld/ErxTask.xcdatamodel/contents
../Sources/eRpApp/Screens/Main/PrescriptionDetail/PrescriptionDetailDomain.swift:188
// Delete
// [REQ:gemSpec_eRp_FdV:A_19229]
case .delete:
../Sources/eRpApp/Screens/Main/PrescriptionDetail/PrescriptionDetailDomain.swift:195
// [REQ:gemSpec_eRp_FdV:A_19229]
case .destination(.presented(.alert(.confirmedDelete))):
A_19480
../Sources/IDP/IDPSession.swift:26
usage of this token is limited to FD/IDP Access.
/// Subscribe to the session's IDPToken and receive the latest (session) token through this Publisher
///
/// [REQ:gemSpec_eRp_FdV:A_19480] usage of this token is limited to FD/IDP Access.
var autoRefreshedToken: AnyPublisher<IDPToken?, IDPError> { get }
../Sources/IDP/IDPStorage.swift:34
usage of this token is limited to FD/IDP Access.
/// Retrieve and set an IDP Token
/// [REQ:gemSpec_eRp_FdV:A_19480] usage of this token is limited to FD/IDP Access.
var token: AnyPublisher<IDPToken?, Never> { get }
A_19739
Implemented with ATS within Info.plist
, path: NSAppTransportSecurity.NSPinnedDomains
../Sources/TrustStore/TrustStoreSession.swift:19
/// Request and validate the VAU certificate
///
/// [REQ:gemSpec_eRp_FdV:A_19739]
///
/// - Returns: A publisher that emits a validated VAU certificate or an error
func loadVauCertificate() -> AnyPublisher<X509, TrustStoreError>
../Sources/TrustStore/TrustStoreSession.swift:29
/// Try to validate a given certificate against the underlying truststore.
/// An OCSP response will also be requested and checked against
///
/// [REQ:gemSpec_eRp_FdV:A_19739]
///
/// - Parameter certificate: the certificate to be validated
/// - Returns: A publisher that emits a Boolean stating whether or not the certificate could be validated.
func validate(certificate: X509) -> AnyPublisher<Bool, TrustStoreError>
A_19979
We use external services: The Apothekenverzeichnis and our analytics framework. During the communication with the pharmacy, there will be data shared via a prescription code. The requirement to this feature is described in gemSpec_eRp_FdV sectin 5.2.3.10 and 5.2.3.11.
A_19980
The user is informed and required to accept this information via the data protection statement. Related data and services are listed in sections 5.
../Sources/eRpApp/Screens/Settings/DataPrivacy/DataPrivacyView.swift:14
Actual View driving the display of DataPrivacy.html
// [REQ:BSI-eRp-ePA:O.Purp_1#2] Actual View driving the display of `DataPrivacy.html`
// [REQ:BSI-eRp-ePA:O.Arch_8#3] Webview containing local html without javascript
// [REQ:gemSpec_eRp_FdV:A_19980#2] Actual View driving the display of `DataPrivacy.html`
struct DataPrivacyView: View {
A_19981
The user is informed and required to accept this information via the data protection statement. Related data and services are listed in sections 5.
A_19982
The agreement to the use of the analytics framework can be revoked. But other agreements cannot be revoked, since the app would not operate properly.
../Sources/eRpApp/Screens/Settings/SettingsDomain.swift:200
Opt out of analytics
// Tracking
// [REQ:gemSpec_eRp_FdV:A_19088, A_19089, A_19092, A_19097] OptIn for usage tracking
// [REQ:BSI-eRp-ePA:O.Purp_5#2] Actual disabling of analytics
// [REQ:gemSpec_eRp_FdV:A_19982#2] Opt out of analytics
case let .toggleTrackingTapped(optIn):
../Sources/eRpApp/Screens/Settings/SettingsView.swift:62
Opt out of analytics
// Tracking comply sheet presentation
// [REQ:BSI-eRp-ePA:O.Purp_5#3] Show comply view for settings triggered analytics enabling
// [REQ:gemSpec_eRp_FdV:A_19982#3] Opt out of analytics
Rectangle()
../Sources/eRpApp/Screens/Settings/SettingsView.swift:238
Opt out of analytics
// [REQ:gemSpec_eRp_FdV:A_19097] Toggle within Settings to enable and disable usage tracking
// [REQ:BSI-eRp-ePA:O.Purp_5#1] Toggle within Settings to enable and disable usage tracking
// [REQ:BSI-eRp-ePA:O.Purp_6#1] Current Analytics state is inspectable by the user
// [REQ:gemSpec_eRp_FdV:A_19982#4] Opt out of analytics
Toggle(isOn: viewStore.binding(
A_19983
All used services except our analytics framework are permitted and attested by the Gematik and under the TI monitoring. The usage of our analytics framework is not under our control, but we exclusively send data to it and receive none.
A_19984
../Sources/Pharmacy/FHIRClient+PharmacyOperation.swift:24
validate pharmacy data format conforming to FHIR
/// Convenience function for searching for pharmacies
///
/// [REQ:gemSpec_eRp_FdV:A_19984] validate pharmacy data format conforming to FHIR
///
/// - Parameters:
/// - searchTerm: Search term
/// - position: Pharmacy position (latitude and longitude)
/// - Returns: `AnyPublisher` that emits a list of `PharmacyLocation`s or is empty when not found
public func searchPharmacies(by searchTerm: String,
../Sources/eRpKit/ScannedErxTask.swift:48
parse task id and access code
// [REQ:gemSpec_eRp_FdV:A_19984] parse task id and access code
guard let taskId = taskString.match(pattern: Self.taskIdRegex) else {
../Sources/eRpKit/ScannedErxTask.swift:103
validate data matrix code structure
// [REQ:gemSpec_eRp_FdV:A_19984] validate data matrix code structure
// [REQ:BSI-eRp-ePA:O.Source_1#4] actual validation by decoding into predefined structure
erxToken = try jsonDecoder.decode(ErxToken.self, from: jsonData)
../Sources/eRpRemoteStorage/FHIRClient+ErxTaskOperation.swift:246
validate pharmacy data format conforming to FHIR
/// Requests all communication Resources for the logged in user
///
/// [REQ:gemSpec_eRp_FdV:A_19984] validate pharmacy data format conforming to FHIR
///
/// - Returns: Array of all loaded communication resources
/// - Parameter referenceDate: Communications with `timestamp` greater or equal `referenceDate` will be fetched
public func communicationResources(
A_20032-01
Note: grace period for OCSP responses is 12h
../Sources/TrustStore/X509TrustStore.swift:14
// [REQ:gemSpec_Krypt:A_21218]
// [REQ:gemSpec_eRp_FdV:A_20032-01]
// Category A: Cross root certificates
private let rootCa: X509
A_20033
Implemented with ATS within Info.plist
, path: NSAppTransportSecurity.NSPinnedDomains
A_20167
../Sources/IDP/IDPInterceptor.swift:61
no token available, bailout
// [REQ:gemSpec_eRp_FdV:A_20167] no token available, bailout
.authentication(error)
../Sources/IDP/IDPInterceptor.swift:71
invalidate/delete unauthorized token
// [REQ:gemSpec_eRp_FdV:A_20167] invalidate/delete unauthorized token
// [REQ:BSI-eRp-ePA:O.Source_5#2] invalidate/delete unauthorized token
self.session.invalidateAccessToken()
../Sources/eRpApp/Data/PrescriptionRepository.swift:133
no token/not authorized, show authenticator module
// [REQ:gemSpec_eRp_FdV:A_20167,A_20172] no token/not authorized, show authenticator module
if Result.success(false) == isAuthenticated {
../Sources/eRpApp/Screens/Pharmacy/RedeemService/RedeemService.swift:144
no token/not authorized, show authenticator module
// [REQ:gemSpec_eRp_FdV:A_20167,A_20172] no token/not authorized, show authenticator module
if Result.success(false) == authenticated {
A_20172
../Sources/eRpApp/Data/PrescriptionRepository.swift:133
no token/not authorized, show authenticator module
// [REQ:gemSpec_eRp_FdV:A_20167,A_20172] no token/not authorized, show authenticator module
if Result.success(false) == isAuthenticated {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.swift:381
// [REQ:gemSpec_eRp_FdV:A_20172]
func idpChallengePublisher(for profileID: UUID) -> AsyncStream<CardWallReadCardDomain.Action> {
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.swift:406
// [REQ:gemSpec_eRp_FdV:A_20172]
// [REQ:gemSpec_IDP_Frontend:A_20526-01] sign and verify with idp
func signChallengeWithNFCCard(can: String,
../Sources/eRpApp/Screens/CardWall/ReadCard/CardWallReadCardDomain.swift:440
// [REQ:gemSpec_eRp_FdV:A_20172]
// [REQ:gemSpec_IDP_Frontend:A_20526-01] verify with idp
private func verifyResultWithIDP(_ signedChallenge: SignedChallenge,
../Sources/eRpApp/Screens/Pharmacy/RedeemService/RedeemService.swift:144
no token/not authorized, show authenticator module
// [REQ:gemSpec_eRp_FdV:A_20167,A_20172] no token/not authorized, show authenticator module
if Result.success(false) == authenticated {
A_20181
Screen that presents the DataMatrix code for redeeming a prescription only contains some static texts and the image of the code.
../Sources/eRpApp/Screens/MatrixCode/MatrixCodeView.swift:10
Screen that presents the DataMatrix code for redeeming a prescription only contains
// [REQ:gemSpec_eRp_FdV:A_20181#1] Screen that presents the DataMatrix code for redeeming a prescription only contains
// some static texts and the image of the code.
struct MatrixCodeView: View {
A_20182
No advertisement or similar is presented in the app. Assigning a prescription to an pharmacy in only possible via the app’s pharmacy search. Pharmacy search results are only based on search term and filter criteria set by the user.
A_20183
../Sources/Pharmacy/PharmacyFHIRDataSource.swift:31
/// API for requesting pharmacies with the passed search term
///
/// [REQ:gemSpec_eRp_FdV:A_20183]
///
/// - Parameter searchTerm: String that send to the server for filtering the pharmacies response
/// - Parameter position: Position (latitude and longitude) of pharmacy
/// - Parameter filter: further filter parameters for pharmacies
/// - Returns: `AnyPublisher` that emits all `PharmacyLocation`s for the given `searchTerm`
public func searchPharmacies(by searchTerm: String,
../Sources/Pharmacy/PharmacyRemoteDataStore.swift:22
/// API for requesting pharmacies with the passed search term
///
/// [REQ:gemSpec_eRp_FdV:A_20183]
///
/// - Parameter searchTerm: String that send to the server for filtering the pharmacies response
/// - Parameter position: Position (latitude and longitude) of pharmacy
/// - Parameter filter: further filter parameters for pharmacies
/// - Returns: `AnyPublisher` that emits all `PharmacyLocation`s for the given `searchTerm`
func searchPharmacies(by searchTerm: String,
../Sources/eRpApp/Screens/Pharmacy/Search/PharmacySearchDomain.swift:174
search results mirrored verbatim, no sorting, no highlighting
// [REQ:gemSpec_eRp_FdV:A_20183] search results mirrored verbatim, no sorting, no highlighting
state.searchState = .searchRunning
A_20184
../Sources/eRpApp/Session/KeychainStorage.swift:125
// [REQ:gemSpec_eRp_FdV:A_20184]
// [REQ:gemSpec_eRp_FdV:A_21328#3] KeychainStorage implementation
// [REQ:BSI-eRp-ePA:O.Tokn_1#4] KeychainStorage implementation
let success: Bool
../Sources/eRpApp/Session/StandardSessionContainer.swift:92
Keychain storage encrypts session/ssl tokens
// [REQ:gemSpec_eRp_FdV:A_21328#2] Keychain storage encrypts session tokens
// [REQ:gemSpec_eRp_FdV:A_20184] Keychain storage encrypts session/ssl tokens
storage: secureUserStore,
../Sources/eRpApp/Session/StandardSessionContainer.swift:110
No persistent storage for idp pairing session
storage: MemoryStorage(), // [REQ:gemSpec_eRp_FdV:A_20184] No persistent storage for idp pairing session
schedulers: schedulers,
A_20185
../Sources/eRpApp/Screens/Settings/SettingsDomain.swift:203
OptOut for user
// [REQ:gemSpec_eRp_FdV:A_20185,A_20187] OptOut for user
state.trackerOptIn = false
A_20186
../Sources/eRpApp/Session/KeychainStorage.swift:249
Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_IDP_Frontend:A_20499,A_20499-1#3] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_eRp_FdV:A_20186] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
set(can: nil)
../Sources/eRpApp/Session/ProfileSecureDataWiper.swift:40
Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_IDP_Frontend:A_20499,A_20499-01#2] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_eRp_FdV:A_20186] Deletion of SSO_TOKEN, ID_TOKEN, AUTH_TOKEN
// [REQ:gemSpec_IDP_Frontend:A_21603] Certificate
storage.wipe()
A_20187
../Sources/eRpApp/Screens/Settings/SettingsDomain.swift:203
OptOut for user
// [REQ:gemSpec_eRp_FdV:A_20185,A_20187] OptOut for user
state.trackerOptIn = false
A_20193
Camera is only used for scanning recipes and avatar setup. CoreLocation is used for pharmacy search. Usage is requested before actual first usage, the user is asked for permission. This is also enforced by the OS.
A_20194
Camera is only used for scanning recipes and avatar setup. CoreLocation is used for pharmacy search. Usage is requested before actual first usage, the user is asked for permission. This is also enforced by the OS.
A_20206
../Sources/HTTPClient/DefaultHTTPClient.swift:41
// [REQ:gemSpec_Krypt:GS-A_4385,A_18467,A_18464,GS-A_4387]
// [REQ:gemSpec_Krypt:GS-A_5322] TODO: Check if limiting SSL Sessions is possible, check for renegotiation
// swiftlint:disable:previous todo
// [REQ:gemSpec_IDP_Frontend:A_20606] Live URLs not present in NSAppTransportSecurity exception list for allowed
// HTTP communication
// [REQ:gemSpec_eRp_FdV:A_20206]
// [REQ:BSI-eRp-ePA:O.Sess_1#2,O.Ntwk_2#2,O.Ntwk_3#2,O.Ntwk_7#2] URLSession is used as Network Framework
A_20208
../Sources/Pharmacy/PharmacyFHIROperation.swift:14
/// Search for pharmacies by name
/// [REQ:gemSpec_eRp_FdV:A_20208]
case searchPharmacies(searchTerm: String, position: Position?, filter: [String: String], handler: Handler)
A_20285
../Sources/eRpApp/Screens/Pharmacy/Search/PharmacySearchDomain.swift:184
pharmacy order is resolved on server side
// [REQ:gemSpec_eRp_FdV:A_20285] pharmacy order is resolved on server side
state.pharmacies = pharmacies.map {
A_20603
../Sources/eRpApp/Screens/Main/PrescriptionDetail/PrescriptionDetailDomain.swift:86
Usages of matrixCodeGenerator for code generation. UserProfile is neither part of
// [REQ:gemSpec_eRp_FdV:A_20603] Usages of matrixCodeGenerator for code generation. UserProfile is neither part of
// the screen nor the state.
@Dependency(\.erxMatrixCodeGenerator) var matrixCodeGenerator: ErxMatrixCodeGenerator
../Sources/eRpApp/Screens/MatrixCode/MatrixCodeDomain.swift:51
Usages of matrixCodeGenerator for code generation. UserProfile is neither part of
// [REQ:gemSpec_eRp_FdV:A_20603] Usages of matrixCodeGenerator for code generation. UserProfile is neither part of
// the screen nor the state.
@Dependency(\.erxMatrixCodeGenerator) var erxMatrixCodeGenerator: ErxMatrixCodeGenerator
A_21324
Token-key and code-verifier are encoded into an JSON object.
../Sources/IDP/internal/TokenPayload.swift:140
Token-key and code-verifier are encoded into KeyVerifier.
// [REQ:gemSpec_eRp_FdV:A_21324#2] Token-key and code-verifier are encoded into KeyVerifier.
public struct KeyVerifier: Codable {
A_21325
AccessToken is encyrpted for each network request to the Fachdienst via VAUClient module.
../Sources/VAUClient/internal/VAUCrypto.swift:84
Encryption of accessToken (here bearerToken)
// [REQ:gemSpec_eRp_FdV:A_21325#2] Encryption of accessToken (here bearerToken)
func encrypt() throws -> Data {
A_21326
ACCESS_TOKEN information is managed by IDPToken structure
../Sources/IDP/Models/IDPToken.swift:12
Structure holding ACCESS_TOKEN and ID_TOKEN information
/// IDPToken
///
/// [REQ:gemSpec_eRp_FdV:A_21326#2,A_21327#2] Structure holding ACCESS_TOKEN and ID_TOKEN information
public struct IDPToken: Codable {
../Sources/IDP/DefaultIDPSession.swift:99
Triggered at every app start, profile change, pull to refresh
// [REQ:gemSpec_eRp_FdV:A_21326#3,A_21327#3] Triggered at every app start, profile change, pull to refresh
autoRefreshedToken.map { token in
../Sources/IDP/DefaultIDPSession.swift:792
Triggered at every app start, profile change, pull to refresh
// [REQ:gemSpec_eRp_FdV:A_21326#4,A_21327#4] Triggered at every app start, profile change, pull to refresh
func refreshIfExpired(session: DefaultIDPSession,
../Sources/IDP/DefaultIDPSession.swift:804
Either return a refreshed IDPToken
// [REQ:gemSpec_eRp_FdV:A_21326#5,A_21327#5] Either return a refreshed IDPToken
// (or nil in case of error) to overwrite the current one
guard let session = session else {
A_21327
ID_TOKEN information is managed by IDPToken structure
../Sources/IDP/Models/IDPToken.swift:12
Structure holding ACCESS_TOKEN and ID_TOKEN information
/// IDPToken
///
/// [REQ:gemSpec_eRp_FdV:A_21326#2,A_21327#2] Structure holding ACCESS_TOKEN and ID_TOKEN information
public struct IDPToken: Codable {
../Sources/IDP/DefaultIDPSession.swift:99
Triggered at every app start, profile change, pull to refresh
// [REQ:gemSpec_eRp_FdV:A_21326#3,A_21327#3] Triggered at every app start, profile change, pull to refresh
autoRefreshedToken.map { token in
../Sources/IDP/DefaultIDPSession.swift:792
Triggered at every app start, profile change, pull to refresh
// [REQ:gemSpec_eRp_FdV:A_21326#4,A_21327#4] Triggered at every app start, profile change, pull to refresh
func refreshIfExpired(session: DefaultIDPSession,
../Sources/IDP/DefaultIDPSession.swift:804
Either return a refreshed IDPToken
// [REQ:gemSpec_eRp_FdV:A_21326#5,A_21327#5] Either return a refreshed IDPToken
// (or nil in case of error) to overwrite the current one
guard let session = session else {
A_21328
Keychain storage encrypts session tokens.
../Sources/eRpApp/Session/StandardSessionContainer.swift:91
Keychain storage encrypts session tokens
// [REQ:gemSpec_eRp_FdV:A_21328#2] Keychain storage encrypts session tokens
// [REQ:gemSpec_eRp_FdV:A_20184] Keychain storage encrypts session/ssl tokens
storage: secureUserStore,
../Sources/eRpApp/Session/KeychainStorage.swift:126
KeychainStorage implementation
// [REQ:gemSpec_eRp_FdV:A_20184]
// [REQ:gemSpec_eRp_FdV:A_21328#3] KeychainStorage implementation
// [REQ:BSI-eRp-ePA:O.Tokn_1#4] KeychainStorage implementation
let success: Bool
A_22778
Encryption of message to the Pharmacy is done with/for all provided certificates/recipients.
../Sources/AVS/AVSCmsEncrypter.swift:10
Encryption of message to the Pharmacy is done for all provided recipients
// [REQ:gemSpec_eRp_FdV:A_22778#2] Encryption of message to the Pharmacy is done for all provided recipients
func cmsEncrypt(_ data: Data, recipients: [X509]) throws -> Data
../Sources/AVS/AVSCmsEncrypter.swift:21
Encryption of message to the Pharmacy is done with all provided certificates
// [REQ:gemSpec_Krypt:GS-A_4390] RSAES-OAEP with MGF1 implemented in sub-framework OpenSSL-swift
// [REQ:gemSpec_eRp_FdV:A_22778#3] Encryption of message to the Pharmacy is done with all provided certificates
// [REQ:gemSpec_eRp_FdV:A_22779#3] Encrypted message is of form of a PKCS#7 container (CMS)
// https://github.com/gematik/OpenSSL-Swift/blob/3c1ea91ba5abfefecfe3588815cb928d777e29ad/Sources/OpenSSL/CMS/CMSContentInfo.swift#L88
// swiftlint:disable:previous line_length
let cms = try CMSContentInfo.encryptPartial(data: data)
A_22779
Encrypted message is of form of a PKCS#7 container (CMS)
../Sources/AVS/AVSMessageConverter.swift:32
Encrypted message is of form of a PKCS#7 container (CMS)
// [REQ:gemSpec_eRp_FdV:A_22779#2] Encrypted message is of form of a PKCS#7 container (CMS)
// 1. Create a CMS AuthenticatedEnvelopedData structure with help from OpenSSL-swift
let cmsAuthEnvelopedData = try avsCmsEncrypter.cmsEncrypt(data, recipients: recipients)
../Sources/AVS/AVSCmsEncrypter.swift:22
Encrypted message is of form of a PKCS#7 container (CMS)
// [REQ:gemSpec_Krypt:GS-A_4390] RSAES-OAEP with MGF1 implemented in sub-framework OpenSSL-swift
// [REQ:gemSpec_eRp_FdV:A_22778#3] Encryption of message to the Pharmacy is done with all provided certificates
// [REQ:gemSpec_eRp_FdV:A_22779#3] Encrypted message is of form of a PKCS#7 container (CMS)
// https://github.com/gematik/OpenSSL-Swift/blob/3c1ea91ba5abfefecfe3588815cb928d777e29ad/Sources/OpenSSL/CMS/CMSContentInfo.swift#L88
// swiftlint:disable:previous line_length
let cms = try CMSContentInfo.encryptPartial(data: data)
gemSpec_eRp_Fdv
A_20032-01
../Sources/TrustStore/X509TrustStore.swift:106
/// Match a collection of `OCSPResponse`s with the end entity certificates of this `X509TrustStore`.
/// Checks response status, revocation status for each certificate and validates the signer certificates of
/// the responses itself.
///
/// [REQ:gemSpec_Krypt:A_21218]
/// [REQ:gemSpec_eRp_Fdv:A_20032-01]
///
/// - Note: This function assumes that up-to-dateness of the responses itself has already been checked.
///
/// - Returns: true on successful matching/validation, false if not successful or error
func checkEeCertificatesStatus(with ocspResponses: [OCSPResponse]) throws -> Bool {