diff --git a/Brewfile b/Brewfile index 9452402..ed1b29e 100644 --- a/Brewfile +++ b/Brewfile @@ -1 +1,2 @@ -brew 'mint' \ No newline at end of file +brew 'mint' +brew 'maven' \ No newline at end of file diff --git a/Cartfile.private b/Cartfile.private index b9e02fc..688bcd0 100644 --- a/Cartfile.private +++ b/Cartfile.private @@ -1,4 +1,4 @@ github "Quick/Nimble" ~> 9.0 -github "tadija/AEXML" "4.5.0" +github "tadija/AEXML" ~> 4.0 github "hectr/swift-stream-reader" "0.3.0" github "swiftsocket/SwiftSocket" "2e6ba27140a29fae8a6331ba4463312e0c71a6b0" diff --git a/Cartfile.resolved b/Cartfile.resolved index 5964c65..a27a90b 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -5,4 +5,4 @@ github "SwiftCommon/DataKit" "1.1.0" github "gematik/OpenSSL-Swift" "3.0.3" github "hectr/swift-stream-reader" "0.3.0" github "swiftsocket/SwiftSocket" "2e6ba27140a29fae8a6331ba4463312e0c71a6b0" -github "tadija/AEXML" "4.5.0" \ No newline at end of file +github "tadija/AEXML" "4.6.1" diff --git a/Gemfile.lock b/Gemfile.lock index 13c2b84..bb4d431 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -308,4 +308,4 @@ RUBY VERSION ruby 2.6.5p114 BUNDLED WITH - 2.3.16 + 2.3.20 diff --git a/IntegrationTests/CardSimulationTerminalTestCase/CardSimulationTerminalTestCase.swift b/IntegrationTests/CardSimulationTerminalTestCase/CardSimulationTerminalTestCase.swift index 07fcaec..6764c53 100644 --- a/IntegrationTests/CardSimulationTerminalTestCase/CardSimulationTerminalTestCase.swift +++ b/IntegrationTests/CardSimulationTerminalTestCase/CardSimulationTerminalTestCase.swift @@ -70,7 +70,7 @@ open class CardSimulationTerminalTestCase: XCTestCase { let manipulators = [cardImagePath, channelContextPath] return CardSimulationTerminalResource(url: config, configManipulators: manipulators, - simulatorVersion: "2.7.9-395") + simulatorVersion: "2.8.4-436") } #endif diff --git a/IntegrationTests/HealthCardControl/HealthCardTypeExtChangeReferenceDataIntegrationTest.swift b/IntegrationTests/HealthCardControl/HealthCardTypeExtChangeReferenceDataIntegrationTest.swift new file mode 100644 index 0000000..904bcc7 --- /dev/null +++ b/IntegrationTests/HealthCardControl/HealthCardTypeExtChangeReferenceDataIntegrationTest.swift @@ -0,0 +1,57 @@ +// +// Copyright (c) 2022 gematik GmbH +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import HealthCardAccess +@testable import HealthCardControl +import Nimble +import XCTest + +final class HealthCardTypeExtChangeReferenceDataIntegrationTest: CardSimulationTerminalTestCase { + override class var configFileInput: String { + "Configuration/configuration_EGK_G2_1_80276883110000095711_GuD_TCP.xml" + } + + override class var healthCardStatusInput: HealthCardStatus { .valid(cardType: .egk(generation: .g2_1)) } + + func testChangeReferenceDataEgk21_success() throws { + let old = "123456" as Format2Pin + let new = "654321" as Format2Pin + + expect( + try Self.healthCard.changeReferenceDataSetNewPin( + old: old, + new: new + ) + .test() + ) == ChangeReferenceDataResponse.success + } + + func testChangeReferenceDataEgk21_wrongPasswordLength() throws { + let old = "123456" as Format2Pin + let new = "654321123456" as Format2Pin + + expect( + try Self.healthCard.changeReferenceDataSetNewPin( + old: old, + new: new, + type: EgkFileSystem.Pin.mrpinHome, + dfSpecific: false + ) + .test() + ) == ChangeReferenceDataResponse.wrongPasswordLength + } +} diff --git a/IntegrationTests/HealthCardControl/HealthCardTypeExtChangeReferenceDataIntegrationTestCont.swift b/IntegrationTests/HealthCardControl/HealthCardTypeExtChangeReferenceDataIntegrationTestCont.swift new file mode 100644 index 0000000..02530ee --- /dev/null +++ b/IntegrationTests/HealthCardControl/HealthCardTypeExtChangeReferenceDataIntegrationTestCont.swift @@ -0,0 +1,69 @@ +// +// Copyright (c) 2022 gematik GmbH +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import HealthCardAccess +@testable import HealthCardControl +import Nimble +import XCTest + +// Note: This continuation of `HealthCardTypeExtChangeReferenceDataIntegrationTest` exists to separate +// the count dependent tests from the other ones. +final class HealthCardTypeExtChangeReferenceDataIntegrationTestCont: CardSimulationTerminalTestCase { + override class var configFileInput: String { + "Configuration/configuration_EGK_G2_1_80276883110000095711_GuD_TCP.xml" + } + + override class var healthCardStatusInput: HealthCardStatus { + .valid(cardType: .egk(generation: .g2_1)) + } + + func testChangeReferenceDataEgk21_wrongSecretWarning() throws { + let wrongOld = "9999999" as Format2Pin + let correctOld = "123456" as Format2Pin + let new = "654321" as Format2Pin + + expect( + try Self.healthCard.changeReferenceDataSetNewPin( + old: wrongOld, + new: new, + type: EgkFileSystem.Pin.mrpinHome, + dfSpecific: false + ) + .test() + ) == ChangeReferenceDataResponse.wrongSecretWarning(retryCount: 2) + + expect( + try Self.healthCard.changeReferenceDataSetNewPin( + old: wrongOld, + new: new, + type: EgkFileSystem.Pin.mrpinHome, + dfSpecific: false + ) + .test() + ) == ChangeReferenceDataResponse.wrongSecretWarning(retryCount: 1) + + expect( + try Self.healthCard.changeReferenceDataSetNewPin( + old: correctOld, + new: new, + type: EgkFileSystem.Pin.mrpinHome, + dfSpecific: false + ) + .test() + ) == ChangeReferenceDataResponse.success + } +} diff --git a/IntegrationTests/HealthCardControl/HealthCardTypeExtVerifyPinTest.swift b/IntegrationTests/HealthCardControl/HealthCardTypeExtVerifyPinTest.swift index d0393fc..5bba45e 100644 --- a/IntegrationTests/HealthCardControl/HealthCardTypeExtVerifyPinTest.swift +++ b/IntegrationTests/HealthCardControl/HealthCardTypeExtVerifyPinTest.swift @@ -36,17 +36,17 @@ final class HealthCardTypeExtVerifyPinTest: CardSimulationTerminalTestCase { } == VerifyPinResponse.success } - func testVerifyMrPinHomeEgk21Failing() { + func testVerifyMrPinHomeEgk21_WarningRetryCounter() { let pinCode = "654321" expect { let format2Pin = try Format2Pin(pincode: pinCode) return try Self.healthCard.verify(pin: format2Pin, type: EgkFileSystem.Pin.mrpinHome) .test() - } == VerifyPinResponse.failed(retryCount: 2) + } == VerifyPinResponse.wrongSecretWarning(retryCount: 2) } static let allTests = [ ("testVerifyMrPinHomeEgk21", testVerifyMrPinHomeEgk21), - ("testVerifyMrPinHomeEgk21Failing", testVerifyMrPinHomeEgk21Failing), + ("testVerifyMrPinHomeEgk21Failing", testVerifyMrPinHomeEgk21_WarningRetryCounter), ] } diff --git a/OpenHealthCardKit.xcodeproj/project.pbxproj b/OpenHealthCardKit.xcodeproj/project.pbxproj index 996d06c..d4b858c 100644 --- a/OpenHealthCardKit.xcodeproj/project.pbxproj +++ b/OpenHealthCardKit.xcodeproj/project.pbxproj @@ -8,11 +8,13 @@ /* Begin PBXBuildFile section */ 0053B2EFD42B14C320AEE765 /* CardChannelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8141371733E8F4F21D525E1 /* CardChannelType.swift */; }; + 008917CB891FE8FFD234B292 /* NFCChangeReferenceDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46E0F19B491067C251CEDD02 /* NFCChangeReferenceDataController.swift */; }; 00A9C9363B471833E5FFE7E2 /* Swift+Reflection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36FAAC44DB2A2CFE6DA27DF5 /* Swift+Reflection.swift */; }; 02051956F3E78036627A65CC /* HCCExtManageSETest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D0163C0C57FEB0E857471FC /* HCCExtManageSETest.swift */; }; 0230A46A4CE4C41992CDC04E /* CommandType+LogicChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6C7413A50D4544529EA24D /* CommandType+LogicChannel.swift */; }; 0523D24E5AB9BD640F720802 /* NFCISO7816APDU+CommandType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45C9336EBD6132C68EDE29DD /* NFCISO7816APDU+CommandType.swift */; }; 059B9C1B1576D5CA7E265008 /* Nimble.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4FC4FBC150A8DB4A3399617A /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 05C53213A2144982D9B750B7 /* HealthCardType+ChangeReferenceData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD83FBA5659DEF0417C8D10 /* HealthCardType+ChangeReferenceData.swift */; }; 063D0A0C504C0E427453E72C /* FileControlParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497EA5052D28E2F9FD88EE35 /* FileControlParameter.swift */; }; 06A0455A685F4AF62F90894E /* EgkFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0743920AABE39147BAA21B87 /* EgkFileSystem.swift */; }; 077FF9DC55210CB49EB4E649 /* NFCCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06222A86585ECB2615E38F6A /* NFCCard.swift */; }; @@ -117,6 +119,7 @@ 440EC2960A7E48DA2F20D934 /* SwiftExtReflectionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B054FF230E7B5408540E11D4 /* SwiftExtReflectionTest.swift */; }; 4440EE67043D530959B0F4E4 /* HealthCardType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75DD21AC793359BBB51EC769 /* HealthCardType.swift */; }; 4574D7004000F059619C4C19 /* Combine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B74094C48C53C2E725AF7F6E /* Combine.framework */; }; + 460C05FA7B86C51C546D6CC6 /* NFCChangeReferenceDataViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA88678C9A960B3A3878E256 /* NFCChangeReferenceDataViewModel.swift */; }; 4716A751F753C830D2DD7F25 /* CardReaderProviderApi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 502537AD9BB0C37AC71B4EA2 /* CardReaderProviderApi.framework */; }; 471969AE115AB80A8326C4C2 /* CardVersion2Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE92266402AB40CCF1D6F68 /* CardVersion2Test.swift */; }; 481B465C47086DDD5B7F2C7A /* CardChannelType+CardAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB4EBC3F6FA32E7C5DF5E5D6 /* CardChannelType+CardAccess.swift */; }; @@ -173,6 +176,7 @@ 65656C7A9F0B597B7335B0CB /* CardObjectIdentifierType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6C09C4CA4D29B2944DE42C /* CardObjectIdentifierType.swift */; }; 66A268D5FD18D9AD5C91569B /* ViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ACD970A3A0968020890956 /* ViewState.swift */; }; 66ADD7EF678498D5B9C2E6C3 /* SignatureAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A4A3C2BECAD8F145054F8D2 /* SignatureAlgorithm.swift */; }; + 673E4D512CC239D967792CBF /* EnvironmentValues+ChangeReferenceDataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4EA9A23D569666A6DBE4554 /* EnvironmentValues+ChangeReferenceDataController.swift */; }; 6796F128763B3CCB0ACB3D76 /* Nimble.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4FC4FBC150A8DB4A3399617A /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 67E84D6282E976F61088B8DE /* ECCurveInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18D34E572CD77E0AB541FB61 /* ECCurveInfo.swift */; }; 68342C456736AD0E4D0D25BC /* CardReaderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4969ED25CEFF0CA516DD7901 /* CardReaderTest.swift */; }; @@ -315,6 +319,7 @@ C4BE60369290B35723C72752 /* HCCExtAccessStructuredDataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4763CB392AD3FA8BE1ED1FD4 /* HCCExtAccessStructuredDataTest.swift */; }; C4D71B0B159C75948B79ADA3 /* HealthCardAccess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04A83549C76227BC6B781C17 /* HealthCardAccess.framework */; }; C59890465D9CDAAB7A8510EC /* ObjCCommonsKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4E96BF70F5C9D49040558EF3 /* ObjCCommonsKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C5E8A2EC54C3AE156901E169 /* HealthCardType+ChangeReferenceData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CD83FBA5659DEF0417C8D10 /* HealthCardType+ChangeReferenceData.swift */; }; C62D42110ECEBE9EDD89D314 /* Combine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B74094C48C53C2E725AF7F6E /* Combine.framework */; }; C76AD8F7BFE07CAFE063ADAE /* CertificateInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D87FCF43392C5E80A3C385 /* CertificateInfo.swift */; }; C7A8C340BE9091E7713F7A66 /* KeyDerivationFunctionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877DDBBEEA8851A73B7AE823 /* KeyDerivationFunctionTest.swift */; }; @@ -337,6 +342,7 @@ D422E98FD061D80E490D35D8 /* ResponseType+APDU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5D3F7F29732260CB45299F /* ResponseType+APDU.swift */; }; D563DB1440234B94CBCC7E2E /* ASN1Kit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE1474F2500A62616F127DEF /* ASN1Kit.xcframework */; }; D58AF3AF64508242B61D5E2D /* CardError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8598DD62F5E852822DC22723 /* CardError.swift */; }; + D6204D541588E2C3E8117018 /* ChangeReferenceDataSetNewPINView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2AF167DE5CD988C68C2AA66 /* ChangeReferenceDataSetNewPINView.swift */; }; D7ABD73568C77E0E7C8AE85F /* GemCommonsKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9A9E37D4C56A885E7954409 /* GemCommonsKit.xcframework */; }; D7D54B24D9E4FE33D2DD217B /* Nimble.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4FC4FBC150A8DB4A3399617A /* Nimble.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; D892282B51CDBBBAF7F70138 /* Password.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B9C7952F9FA83EE0F468F02 /* Password.swift */; }; @@ -360,6 +366,7 @@ E25A0E3E981242C48503FAA5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A73E822ACCAA04F10D455DC8 /* SceneDelegate.swift */; }; E272AC8A6F4564805AC2DBE5 /* HealthCardCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AEABF3C46C855D4BC7B81A3 /* HealthCardCommand.swift */; }; E3DBD4DC1F14811224CD86DC /* HCCExtAccessStructuredDataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4763CB392AD3FA8BE1ED1FD4 /* HCCExtAccessStructuredDataTest.swift */; }; + E4089B94DB85FF4392AC04E6 /* CoreNFCError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F925845FE9F69B175A571C7 /* CoreNFCError.swift */; }; E659396E4990A11329B52172 /* Combine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B74094C48C53C2E725AF7F6E /* Combine.framework */; }; E6C89A803472F5115DCD8A47 /* HealthCardAccess+LocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C6D8A8ABB2D2303689A2547 /* HealthCardAccess+LocalizedError.swift */; }; E785C72BF5CB7EF16085EC07 /* HealthCardCommandBuilderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F878089E532BB761296290 /* HealthCardCommandBuilderTest.swift */; }; @@ -835,9 +842,11 @@ 19591A6885C16B86354EE712 /* CommandType+APDU.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CommandType+APDU.swift"; sourceTree = ""; }; 1A4A110124E2632B3BC2FDB4 /* HealthCardControl.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HealthCardControl.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1AAA4E3F1F8619F712660ED5 /* CardReaderProviderApi+LocalizedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardReaderProviderApi+LocalizedError.swift"; sourceTree = ""; }; + 1CD83FBA5659DEF0417C8D10 /* HealthCardType+ChangeReferenceData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HealthCardType+ChangeReferenceData.swift"; sourceTree = ""; }; 1D09475EC3F530AF97BD714E /* CAN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CAN.swift; sourceTree = ""; }; 1DBA638AEC6CBDA59D6B4ED4 /* FileControlParameterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileControlParameterTest.swift; sourceTree = ""; }; 1F4872A78D8C0DEF5B8D9457 /* CardType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardType.swift; sourceTree = ""; }; + 1F925845FE9F69B175A571C7 /* CoreNFCError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreNFCError.swift; sourceTree = ""; }; 222840D7A5008B03D31E010C /* SecureMessaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureMessaging.swift; sourceTree = ""; }; 22C53A91DF1C5499D93AE1B6 /* HealthCardCommandBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthCardCommandBuilder.swift; sourceTree = ""; }; 22C797EFA88B41201EC67667 /* NFCCardChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFCCardChannel.swift; sourceTree = ""; }; @@ -863,6 +872,7 @@ 455ABDA8B8E87644B18F14AE /* CallOnMainThread.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallOnMainThread.swift; sourceTree = ""; }; 45C9336EBD6132C68EDE29DD /* NFCISO7816APDU+CommandType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NFCISO7816APDU+CommandType.swift"; sourceTree = ""; }; 46C0AA435DF43638D17CD1FD /* CardGeneration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardGeneration.swift; sourceTree = ""; }; + 46E0F19B491067C251CEDD02 /* NFCChangeReferenceDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFCChangeReferenceDataController.swift; sourceTree = ""; }; 4763CB392AD3FA8BE1ED1FD4 /* HCCExtAccessStructuredDataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HCCExtAccessStructuredDataTest.swift; sourceTree = ""; }; 4969ED25CEFF0CA516DD7901 /* CardReaderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderTest.swift; sourceTree = ""; }; 497EA5052D28E2F9FD88EE35 /* FileControlParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileControlParameter.swift; sourceTree = ""; }; @@ -980,6 +990,7 @@ D8141371733E8F4F21D525E1 /* CardChannelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardChannelType.swift; sourceTree = ""; }; D943174FC3F0C497C879DD32 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; DA2A8B64E2097BA2285F5FB8 /* UInt8+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt8+Data.swift"; sourceTree = ""; }; + DA88678C9A960B3A3878E256 /* NFCChangeReferenceDataViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFCChangeReferenceDataViewModel.swift; sourceTree = ""; }; DD3129E9C77F08BF888B6C8E /* NFCTagReaderSession+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NFCTagReaderSession+Publisher.swift"; sourceTree = ""; }; DD528D91A92D9A303B1F795F /* Resources.bundle */ = {isa = PBXFileReference; lastKnownFileType = wrapper.cfbundle; path = Resources.bundle; sourceTree = ""; }; DDE92266402AB40CCF1D6F68 /* CardVersion2Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardVersion2Test.swift; sourceTree = ""; }; @@ -996,10 +1007,12 @@ F004317A4D8B351D3DD747D7 /* HealthCardCommand+UserVerification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HealthCardCommand+UserVerification.swift"; sourceTree = ""; }; F13264DBC352EDEFC4EB4838 /* HCCExtAuthenticationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HCCExtAuthenticationTest.swift; sourceTree = ""; }; F1A27E7376148B452A98EA0F /* Data+Normalize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Normalize.swift"; sourceTree = ""; }; + F2AF167DE5CD988C68C2AA66 /* ChangeReferenceDataSetNewPINView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeReferenceDataSetNewPINView.swift; sourceTree = ""; }; F2B16C08C1A96471C10F9369 /* RegisterPINViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterPINViewSnapshotTests.swift; sourceTree = ""; }; F362A9FB9C3E65DF95C76F4C /* ProviderDescriptorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderDescriptorType.swift; sourceTree = ""; }; F3CB6F825584F0CBD9BF11A9 /* HCCExtObjectSystemManagementTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HCCExtObjectSystemManagementTest.swift; sourceTree = ""; }; F4D87FCF43392C5E80A3C385 /* CertificateInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateInfo.swift; sourceTree = ""; }; + F4EA9A23D569666A6DBE4554 /* EnvironmentValues+ChangeReferenceDataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+ChangeReferenceDataController.swift"; sourceTree = ""; }; F5787448FAEE55FA2B9AE165 /* NFCLoginController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFCLoginController.swift; sourceTree = ""; }; F71D27734E383B05E6431FB7 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; F7D95C76EA2CCE25069CB937 /* CardChannelType+Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CardChannelType+Version.swift"; sourceTree = ""; }; @@ -1238,6 +1251,7 @@ isa = PBXGroup; children = ( DE1D500D64701B59ADFC2108 /* HealthCardType+Authenticate.swift */, + 1CD83FBA5659DEF0417C8D10 /* HealthCardType+ChangeReferenceData.swift */, 5FEBB8699A6C52AF718C69E0 /* HealthCardType+ESIGN.swift */, 71C162FDB08E37D5FF1BD693 /* HealthCardType+ResetRetryCounter.swift */, A6BB466646265F8FEC798B44 /* HealthCardType+VerifyPin.swift */, @@ -1259,6 +1273,7 @@ children = ( B2CDF0F8988EF4C258C20E73 /* AppDelegate.swift */, 455ABDA8B8E87644B18F14AE /* CallOnMainThread.swift */, + F4EA9A23D569666A6DBE4554 /* EnvironmentValues+ChangeReferenceDataController.swift */, CC5910A1B4725CB221B86FA6 /* EnvironmentValues+LoginController.swift */, 983DE2F40F74DC348311038E /* EnvironmentValues+ResetPINController.swift */, 4AC2ABE74020F1FE110D4C3F /* KeyboardHeight.swift */, @@ -1478,6 +1493,8 @@ isa = PBXGroup; children = ( 2AC56487B81D527366CBF163 /* ActivityViewController.swift */, + F2AF167DE5CD988C68C2AA66 /* ChangeReferenceDataSetNewPINView.swift */, + DA88678C9A960B3A3878E256 /* NFCChangeReferenceDataViewModel.swift */, A6BDB4588EC5E0F93B5D19A8 /* NFCLoginViewModel.swift */, B05F4BE35458D96A5BACA321 /* NFCResetRetryCounterViewModel.swift */, 05A519605CB8EB9F9CEE5074 /* ReadingResultsView.swift */, @@ -1657,6 +1674,7 @@ 9C6D8A8ABB2D2303689A2547 /* HealthCardAccess+LocalizedError.swift */, 773D325D6DC33EBABA73491F /* HealthCardControl+LocalizedError.swift */, D39A3141CD1F779B421E16A2 /* NFCCardReaderProvider+LocalizedError.swift */, + 46E0F19B491067C251CEDD02 /* NFCChangeReferenceDataController.swift */, F5787448FAEE55FA2B9AE165 /* NFCLoginController.swift */, 87BAA0CD1F21493AED05A990 /* NFCResetRetryCounterController.swift */, ); @@ -1703,6 +1721,7 @@ AFC0E91A56BC5FD07FDEE5DF /* Reader */ = { isa = PBXGroup; children = ( + 1F925845FE9F69B175A571C7 /* CoreNFCError.swift */, DD3129E9C77F08BF888B6C8E /* NFCTagReaderSession+Publisher.swift */, ); path = Reader; @@ -2415,6 +2434,7 @@ FCE097F1F03DA30F8EF5AB9C /* Data+Secure.swift in Sources */, C937FCE2C5895DF705A91B5C /* HealthCard+Error.swift in Sources */, A73CBAEC4336180D12B4FAC2 /* HealthCardType+Authenticate.swift in Sources */, + C5E8A2EC54C3AE156901E169 /* HealthCardType+ChangeReferenceData.swift in Sources */, F5B02C8B985DBE417E53F30E /* HealthCardType+ESIGN.swift in Sources */, 8EC99E768EF8981F621B0CD7 /* HealthCardType+ReadFile.swift in Sources */, AE909FDB9955127D247CE663 /* HealthCardType+ResetRetryCounter.swift in Sources */, @@ -2623,7 +2643,9 @@ 137904AB8121100E43DCF1EE /* AppDelegate.swift in Sources */, 1019DA8B00AC78D95FF6E8FF /* CallOnMainThread.swift in Sources */, F10DD148478CB1A52CE6B993 /* CardReaderProviderApi+LocalizedError.swift in Sources */, + D6204D541588E2C3E8117018 /* ChangeReferenceDataSetNewPINView.swift in Sources */, 554D960FD60E6C3159C8021F /* Colors.swift in Sources */, + 673E4D512CC239D967792CBF /* EnvironmentValues+ChangeReferenceDataController.swift in Sources */, 924B4B478B4F992306D532D4 /* EnvironmentValues+LoginController.swift in Sources */, 6E3B40FE116A9359B587ECAF /* EnvironmentValues+ResetPINController.swift in Sources */, 22D793FD509BFA971488D7AE /* GTextButton.swift in Sources */, @@ -2631,6 +2653,8 @@ 68E769933CE8551F3B13ED20 /* HealthCardControl+LocalizedError.swift in Sources */, D94CAC4EA32209809B17B653 /* KeyboardHeight.swift in Sources */, 0EC4BFD7A018B077EA55F21E /* NFCCardReaderProvider+LocalizedError.swift in Sources */, + 008917CB891FE8FFD234B292 /* NFCChangeReferenceDataController.swift in Sources */, + 460C05FA7B86C51C546D6CC6 /* NFCChangeReferenceDataViewModel.swift in Sources */, AE4338226632E9561E5249D9 /* NFCLoginController.swift in Sources */, 13C1819F56CE403E33855688 /* NFCLoginViewModel.swift in Sources */, AAC5223EEC5AE3C01010C2D1 /* NFCResetRetryCounterController.swift in Sources */, @@ -2681,6 +2705,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E4089B94DB85FF4392AC04E6 /* CoreNFCError.swift in Sources */, 077FF9DC55210CB49EB4E649 /* NFCCard.swift in Sources */, 2DEA1250BCF564F107B41C50 /* NFCCardChannel.swift in Sources */, CE6798EFAE8792707E901ED1 /* NFCCardError.swift in Sources */, @@ -2702,6 +2727,7 @@ 36802F75260566417BAA562A /* Data+Secure.swift in Sources */, DDB34857CD516AEA70B86514 /* HealthCard+Error.swift in Sources */, ABE179133B6D7F825E9257E8 /* HealthCardType+Authenticate.swift in Sources */, + 05C53213A2144982D9B750B7 /* HealthCardType+ChangeReferenceData.swift in Sources */, 1ABA686B69824A523EA8693B /* HealthCardType+ESIGN.swift in Sources */, 5FC54D6D82C9E542474BD903 /* HealthCardType+ReadFile.swift in Sources */, FDB4412B11235E39F6449389 /* HealthCardType+ResetRetryCounter.swift in Sources */, diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 1d5da3b..96441c4 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,3 +1,22 @@ +# Release 5.0.0 + +## Breaking + + - Change `VerifyPinResponse` to bridge all possible verify responses + +## Added + + - Add error type `CoreNFCError` for all errors occurring in CoreNFC + - Add convenience interfaces for PIN-verification + +## Changed + + - Change ResetRetryCounter to set a new PIN with ChangeReferenceDate + +## Internals + + - Revive Integration Tests + # Release 4.1.0 ## Added diff --git a/Sources/HealthCardControl/Authentication/HealthCardType+ChangeReferenceData.swift b/Sources/HealthCardControl/Authentication/HealthCardType+ChangeReferenceData.swift new file mode 100644 index 0000000..d7690d6 --- /dev/null +++ b/Sources/HealthCardControl/Authentication/HealthCardType+ChangeReferenceData.swift @@ -0,0 +1,120 @@ +// +// Copyright (c) 2022 gematik GmbH +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation +import HealthCardAccess +import Helper + +public enum ChangeReferenceDataResponse: Equatable { + /// Reset successful + case success + /// Reset failed, retry count is the number of retries left for the given `EgkFileSystem.Pin` type + case wrongSecretWarning(retryCount: Int) + /// Access rule evaluation failure + case securityStatusNotSatisfied + /// Write action unsuccessful + case memoryFailure + /// Counter for PUK is already blocked (cannot be reset) + case commandBlocked + /// New password is either too long or too short + case wrongPasswordLength + /// Referenced password could not be found + case passwordNotFound + /// Any (unexpected) error not specified in gemSpec_COS 14.6.1.3 + case unknownFailure +} + +/// Convenience password reference selector +public enum ChangeReferenceDataAffectedPassword { + /// MR.PIN HOME in non-df-specific context + case mrPinHomeNoDfSpecific +} + +extension HealthCardType { + /// Assign a new secret (value) to a password. + /// + /// - Parameters: + /// - old: The old secret of the password object + /// - new: The new secret of the password object + /// - type: Password reference + /// - dfSpecific: is Password reference dfSpecific + /// - Returns: Publisher that tries to set the password's new value + public func changeReferenceDataSetNewPin( + old: Format2Pin, + new: Format2Pin, + type: EgkFileSystem.Pin = EgkFileSystem.Pin.mrpinHome, + dfSpecific: Bool = false + ) -> AnyPublisher { + CommandLogger.commands.append(Command(message: "Change Reference Data: Set New PIN", type: .description)) + let command: HealthCardCommand + let parameters = (password: type.rawValue, dfSpecific: dfSpecific, old: old, new: new) + do { + command = try HealthCardCommand.ChangeReferenceData.change(password: parameters) + } catch { + return Combine.Fail(error: error).eraseToAnyPublisher() + } + + return command + .publisher(for: self) + .map { response -> ChangeReferenceDataResponse in + let responseStatus = response.responseStatus + if ResponseStatus.wrongSecretWarnings.contains(responseStatus) { + return .wrongSecretWarning(retryCount: responseStatus.retryCount) + } + switch responseStatus { + case .success: return .success + case .memoryFailure: return .memoryFailure + case .securityStatusNotSatisfied: return .securityStatusNotSatisfied + case .commandBlocked: return .commandBlocked + case .wrongPasswordLength: return .wrongPasswordLength + case .passwordNotFound: return .passwordNotFound + default: return .unknownFailure + } + } + .eraseToAnyPublisher() + } + + /// Assign a new secret (value) to a password. + /// + /// - Parameters: + /// - old: The old secret of the password object + /// - new: The new secret of the password object + /// - affectedPassword: convenient `ChangeReferenceDataAffectedPassword` selector + /// - Returns: Publisher that tries to set the password's new value + public func changeReferenceDataSetNewPin( + old: String, + new: String, + affectedPassword: ChangeReferenceDataAffectedPassword + ) -> AnyPublisher { + let parsedOld: Format2Pin + let parsedNew: Format2Pin + do { + parsedOld = try Format2Pin(pincode: old) + parsedNew = try Format2Pin(pincode: new) + } catch { + return Fail(error: error).eraseToAnyPublisher() + } + let type: EgkFileSystem.Pin + let dfSpecific: Bool + switch affectedPassword { + case .mrPinHomeNoDfSpecific: + type = .mrpinHome + dfSpecific = false + } + return changeReferenceDataSetNewPin(old: parsedOld, new: parsedNew, type: type, dfSpecific: dfSpecific) + } +} diff --git a/Sources/HealthCardControl/Authentication/HealthCardType+ResetRetryCounter.swift b/Sources/HealthCardControl/Authentication/HealthCardType+ResetRetryCounter.swift index 14a021f..1962130 100644 --- a/Sources/HealthCardControl/Authentication/HealthCardType+ResetRetryCounter.swift +++ b/Sources/HealthCardControl/Authentication/HealthCardType+ResetRetryCounter.swift @@ -24,6 +24,7 @@ public enum ResetRetryCounterResponse: Equatable { case success /// Reset failed, retry count is the number of retries left for the given `EgkFileSystem.Pin` type's PUK case wrongSecretWarning(retryCount: Int) + /// Access rule evaluation failure case securityStatusNotSatisfied /// Write action unsuccessful case memoryFailure @@ -94,7 +95,7 @@ extension HealthCardType { /// /// - Parameters: /// - puk: Secret which authorizes the action - /// - affectedPassWord: convenient `ResetRetryCounterAffectedPassword` selector + /// - affectedPassWord: convenience `ResetRetryCounterAffectedPassword` selector /// - Returns: Publisher that tries to reset the password's retry counter /// - Throws: HealthCardAccessError public func resetRetryCounter( @@ -125,7 +126,6 @@ extension HealthCardType { /// - type: Password reference /// - dfSpecific: is Password reference dfSpecific /// - Returns: Publisher that tries to reset the password's retry counter while setting a new secret - /// - Throws: HealthCardCommandBuilderError public func resetRetryCounterAndSetNewPin( puk: Format2Pin, newPin: Format2Pin, @@ -170,9 +170,8 @@ extension HealthCardType { /// - Parameters: /// - puk: Secret which authorizes the action /// - newPin: The new secret of the password object - /// - affectedPassWord: convenient `ResetRetryCounterAffectedPassword` selector + /// - affectedPassWord: convenience `ResetRetryCounterAffectedPassword` selector /// - Returns: Publisher that tries to reset the password's retry counter - /// - Throws: HealthCardAccessError public func resetRetryCounterAndSetNewPin( puk: String, newPin: String, diff --git a/Sources/HealthCardControl/Authentication/HealthCardType+VerifyPin.swift b/Sources/HealthCardControl/Authentication/HealthCardType+VerifyPin.swift index acf200e..9df5b8f 100644 --- a/Sources/HealthCardControl/Authentication/HealthCardType+VerifyPin.swift +++ b/Sources/HealthCardControl/Authentication/HealthCardType+VerifyPin.swift @@ -27,7 +27,25 @@ public enum VerifyPinResponse: Equatable { /// Pin verification succeeded case success /// Pin verification failed, retry count is the number of retries left for the given `EgkFileSystem.Pin` type - case failed(retryCount: Int) // TODO: not complete // swiftlint:disable:this todo + case wrongSecretWarning(retryCount: Int) + /// Access rule evaluation failure + case securityStatusNotSatisfied + /// Write action unsuccessful + case memoryFailure + /// Exhausted retry counter + case passwordBlocked + /// Password is transport protected + case passwordNotUsable + /// Referenced password could not be found + case passwordNotFound + /// Any (unexpected) error not specified in gemSpec_COS 14.6.6.2 + case unknownFailure +} + +/// Convenience password reference selector +public enum VerifyPinAffectedPassword { + /// MR.PIN HOME in non-df-specific context + case mrPinHomeNoDfSpecific } extension HealthCardType { @@ -36,24 +54,61 @@ extension HealthCardType { /// - Parameters: /// - pin: `Format2Pin` holds the Pin information for the `type`. E.g. mrPinHome. /// - type: verification type. Any of `EgkFileSystem.Pin`. + /// - dfSpecific: is Password reference dfSpecific /// - /// - Returns: Publisher that tries to verify the given `pin` information against `type` + /// - Returns: Publisher that tries to verify the given PIN-value information against `type` /// /// - Note: Only supports eGK Card types - public func verify(pin: Format2Pin, type: EgkFileSystem.Pin) -> AnyPublisher { + public func verify( + pin: Format2Pin, + type: EgkFileSystem.Pin, + dfSpecific: Bool = false + ) -> AnyPublisher { CommandLogger.commands.append(Command(message: "Verify PIN", type: .description)) - let verifyPasswordParameter = (type.rawValue, false, pin) + let verifyPasswordParameter = (type.rawValue, dfSpecific, pin) return HealthCardCommand.Verify.verify(password: verifyPasswordParameter) .publisher(for: self) - .map { response in - if response.responseStatus == .success { - return .success - } else { - // TODO: not complete // swiftlint:disable:this todo - // is also wrong for failures that were not wrongSecret - return .failed(retryCount: response.responseStatus.retryCount) + .map { response -> VerifyPinResponse in + let responseStatus = response.responseStatus + if ResponseStatus.wrongSecretWarnings.contains(responseStatus) { + return .wrongSecretWarning(retryCount: responseStatus.retryCount) + } + switch responseStatus { + case .success: return .success + case .memoryFailure: return .memoryFailure + case .securityStatusNotSatisfied: return .securityStatusNotSatisfied + case .passwordBlocked: return .passwordBlocked + case .passwordNotUsable: return .passwordNotUsable + case .passwordNotFound: return .passwordNotFound + default: return .unknownFailure } } .eraseToAnyPublisher() } + + /// Verify Password for a Pin type + /// + /// - Parameters: + /// - pin: holds the Pin information for the password + /// - affectedPassword: convenience `VerifyPinAffectedPassword` selector + /// - Returns: Publisher that tries to verify the given PIN-value information against the affected password + public func verify( + pin: String, + affectedPassword: VerifyPinAffectedPassword + ) -> AnyPublisher { + let parsedPIN: Format2Pin + do { + parsedPIN = try Format2Pin(pincode: pin) + } catch { + return Fail(error: error).eraseToAnyPublisher() + } + let type: EgkFileSystem.Pin + let dfSpecific: Bool + switch affectedPassword { + case .mrPinHomeNoDfSpecific: + type = .mrpinHome + dfSpecific = false + } + return verify(pin: parsedPIN, type: type, dfSpecific: dfSpecific) + } } diff --git a/Sources/NFCCardReaderProvider/Card/NFCCardChannel.swift b/Sources/NFCCardReaderProvider/Card/NFCCardChannel.swift index 264549c..52be440 100644 --- a/Sources/NFCCardReaderProvider/Card/NFCCardChannel.swift +++ b/Sources/NFCCardReaderProvider/Card/NFCCardChannel.swift @@ -87,8 +87,8 @@ class NFCCardChannel: CardChannelType { DLog("NFC send timed out [\(sendHeader)]") throw NFCCardError.sendTimeout.connectionError } - if let error = error { - throw error + if let error = error?.asCoreNFCError() { + throw NFCCardError.nfcTag(error: error) } let response = "[\(Data(data + [sw1, sw2]).hexString())]" diff --git a/Sources/NFCCardReaderProvider/Card/NFCCardError.swift b/Sources/NFCCardReaderProvider/Card/NFCCardError.swift index cc97b55..29d9b5d 100644 --- a/Sources/NFCCardReaderProvider/Card/NFCCardError.swift +++ b/Sources/NFCCardReaderProvider/Card/NFCCardError.swift @@ -18,6 +18,7 @@ import CardReaderProviderApi import Foundation public enum NFCCardError: Swift.Error { + case nfcTag(error: CoreNFCError) case noCardPresent case transferException(name: String) case sendTimeout diff --git a/Sources/NFCCardReaderProvider/Reader/CoreNFCError.swift b/Sources/NFCCardReaderProvider/Reader/CoreNFCError.swift new file mode 100644 index 0000000..75f52f6 --- /dev/null +++ b/Sources/NFCCardReaderProvider/Reader/CoreNFCError.swift @@ -0,0 +1,68 @@ +// +// Copyright (c) 2022 gematik GmbH +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import CoreNFC +import Foundation + +public enum CoreNFCError: Swift.Error { + case tagConnectionLost(NFCReaderError) + case sessionTimeout(NFCReaderError) + case sessionInvalidated(NFCReaderError) + case userCanceled(NFCReaderError) + case unsupportedFeature(NFCReaderError) + case other(NFCReaderError) + case unknown(Swift.Error) +} + +extension Swift.Error { + func asCoreNFCError() -> CoreNFCError { + if let nfcReaderError = self as? NFCReaderError { + switch nfcReaderError.code { + case .readerTransceiveErrorTagConnectionLost, + .readerTransceiveErrorTagResponseError: + return .tagConnectionLost(nfcReaderError) + case .readerTransceiveErrorSessionInvalidated, + .readerSessionInvalidationErrorSessionTerminatedUnexpectedly: + return .sessionInvalidated(nfcReaderError) + case .readerSessionInvalidationErrorSessionTimeout: + return .sessionTimeout(nfcReaderError) + case .readerSessionInvalidationErrorUserCanceled: + return .userCanceled(nfcReaderError) + case .readerErrorUnsupportedFeature: + return .unsupportedFeature(nfcReaderError) + case .readerErrorSecurityViolation, + .readerErrorInvalidParameter, + .readerErrorInvalidParameterLength, + .readerErrorParameterOutOfBound, + .readerErrorRadioDisabled, + .readerTransceiveErrorRetryExceeded, + .readerTransceiveErrorTagNotConnected, + .readerTransceiveErrorPacketTooLong, + .readerSessionInvalidationErrorSystemIsBusy, + .readerSessionInvalidationErrorFirstNDEFTagRead, + .tagCommandConfigurationErrorInvalidParameters, + .ndefReaderSessionErrorTagNotWritable, + .ndefReaderSessionErrorTagUpdateFailure, + .ndefReaderSessionErrorTagSizeTooSmall, + .ndefReaderSessionErrorZeroLengthMessage: + return .other(nfcReaderError) + @unknown default: + return .unknown(self) + } + } + return .unknown(self) + } +} diff --git a/Sources/NFCCardReaderProvider/Reader/NFCTagReaderSession+Publisher.swift b/Sources/NFCCardReaderProvider/Reader/NFCTagReaderSession+Publisher.swift index 5e48aae..da1293f 100644 --- a/Sources/NFCCardReaderProvider/Reader/NFCTagReaderSession+Publisher.swift +++ b/Sources/NFCCardReaderProvider/Reader/NFCTagReaderSession+Publisher.swift @@ -39,8 +39,7 @@ extension NFCTagReaderSession { public enum Error: Swift.Error { case couldNotInitializeSession case unsupportedTag - case nfcTag(error: Swift.Error) - case userCancelled(error: Swift.Error) + case nfcTag(error: CoreNFCError) } public struct Publisher: Combine.Publisher { @@ -217,16 +216,8 @@ extension NFCTagReaderSession.Publisher { func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Swift.Error) { DLog("NFC reader session was invalidated: \(error)") - if (error as NSError).code == 200 { // error on session.invalidate() - if card != nil { - // If there is a card the process finished successful - complete(with: nil) - } else { - complete(with: .userCancelled(error: error)) - } - } else { - complete(with: .nfcTag(error: error)) - } + let coreNFCError = error.asCoreNFCError() + complete(with: .nfcTag(error: coreNFCError)) demand = .none } @@ -259,7 +250,7 @@ extension NFCTagReaderSession.Publisher { // Connect to tag session.connect(to: tag) { [weak self] (error: Swift.Error?) in guard let self = self else { return } - if let error = error { + if let error = error?.asCoreNFCError() { session.invalidate(errorMessage: self.messages.connectionErrorMessage) self.complete(with: .nfcTag(error: error)) return diff --git a/Sources/NFCDemo/EnvironmentValues+ChangeReferenceDataController.swift b/Sources/NFCDemo/EnvironmentValues+ChangeReferenceDataController.swift new file mode 100644 index 0000000..361a7af --- /dev/null +++ b/Sources/NFCDemo/EnvironmentValues+ChangeReferenceDataController.swift @@ -0,0 +1,28 @@ +// +// Copyright (c) 2022 gematik GmbH +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct ChangeReferenceDataControllerKey: EnvironmentKey { + static let defaultValue: ChangeReferenceData = NFCChangeReferenceDataController() +} + +extension EnvironmentValues { + var changeReferenceDataController: ChangeReferenceData { + get { self[ChangeReferenceDataControllerKey.self] } + set { self[ChangeReferenceDataControllerKey.self] = newValue } + } +} diff --git a/Sources/NFCDemo/NFC/NFCCardReaderProvider+LocalizedError.swift b/Sources/NFCDemo/NFC/NFCCardReaderProvider+LocalizedError.swift index 81beb00..4b34210 100644 --- a/Sources/NFCDemo/NFC/NFCCardReaderProvider+LocalizedError.swift +++ b/Sources/NFCDemo/NFC/NFCCardReaderProvider+LocalizedError.swift @@ -26,9 +26,29 @@ extension NFCTagReaderSession.Error: LocalizedError { case .unsupportedTag: return "NFCTagReaderSession.Error: The read tag is not supported" case let .nfcTag(error: error): - return error.localizedDescription - case let .userCancelled(error: error): - return error.localizedDescription + switch error { + case let .tagConnectionLost(nFCReaderError): + return nFCReaderError.localizedDescription + case let .sessionTimeout(nFCReaderError): + return nFCReaderError.localizedDescription + + case let .sessionInvalidated(nFCReaderError): + return nFCReaderError.localizedDescription + + case let .userCanceled(nFCReaderError): + return nFCReaderError.localizedDescription + + case let .unsupportedFeature(nFCReaderError): + return nFCReaderError.localizedDescription + + case let .other(nFCReaderError): + return nFCReaderError.localizedDescription + + case let .unknown(error): + return error.localizedDescription + @unknown default: + return "unknown NFCTagReaderSession.nfcTag error" + } @unknown default: return "unknown NFCTagReaderSession.Error" } @@ -44,6 +64,25 @@ extension NFCCardError: LocalizedError { return "NFCCardError: transfer exception with name: \(name)" case .sendTimeout: return "NFCCardError: send timeout" + case let .nfcTag(error: coreNFCError): + switch coreNFCError { + case let .tagConnectionLost(readerError): + return readerError.localizedDescription + case let .sessionTimeout(readerError): + return readerError.localizedDescription + case let .sessionInvalidated(readerError): + return readerError.localizedDescription + case let .other(readerError): + return readerError.localizedDescription + case let .unknown(error): + return error.localizedDescription + case let .userCanceled(readerError): + return readerError.localizedDescription + case let .unsupportedFeature(readerError): + return readerError.localizedDescription + @unknown default: + return "unknown NFCCardError.CoreNFCError" + } @unknown default: return "unknown NFCCardError" } diff --git a/Sources/NFCDemo/NFC/NFCChangeReferenceDataController.swift b/Sources/NFCDemo/NFC/NFCChangeReferenceDataController.swift new file mode 100644 index 0000000..fb00503 --- /dev/null +++ b/Sources/NFCDemo/NFC/NFCChangeReferenceDataController.swift @@ -0,0 +1,183 @@ +// +// Copyright (c) 2022 gematik GmbH +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import CardReaderProviderApi +import Combine +import CoreNFC +import Foundation +import HealthCardAccess +import HealthCardControl +import Helper +import NFCCardReaderProvider + +public class NFCChangeReferenceDataController: ChangeReferenceData { + public enum Error: Swift.Error, LocalizedError { + /// In case the PIN, PUK or CAN could not be constructed from input + case cardError(NFCTagReaderSession.Error) + case invalidCanOrPinFormat + case wrongPin(retryCount: Int) + case commandBlocked + case otherError + + public var errorDescription: String? { + switch self { + case let .cardError(error): + return error.localizedDescription + case .invalidCanOrPinFormat: + return "Invalid CAN or PIN format" + case let .wrongPin(retryCount: retryCount): + return "Wrong PIN with retry count \(retryCount)." + case .commandBlocked: + return "PIN usage counter exhausted" + case .otherError: + return "An unexpected error occurred." + } + } + } + + @Published + private var pState: ViewState = .idle + var state: Published>.Publisher { + $pState + } + + var cancellable: AnyCancellable? + + func dismissError() { + if pState.error != nil { + callOnMainThread { + self.pState = .idle + } + } + } + + let messages = NFCTagReaderSession.Messages( + discoveryMessage: NSLocalizedString("nfc_txt_discoveryMessage", comment: ""), + connectMessage: NSLocalizedString("nfc_txt_connectMessage", comment: ""), + noCardMessage: NSLocalizedString("nfc_txt_noCardMessage", comment: ""), + multipleCardsMessage: NSLocalizedString("nfc_txt_multipleCardsMessage", comment: ""), + unsupportedCardMessage: NSLocalizedString("nfc_txt_unsupportedCardMessage", comment: ""), + connectionErrorMessage: NSLocalizedString("nfc_txt_connectionErrorMessage", comment: "") + ) + + // swiftlint:disable:next function_body_length + func changeReferenceDataSetNewPin(can: String, oldPin: String, newPin: String) { + if case .loading = pState { return } + callOnMainThread { + self.pState = .loading(nil) + } + let canData: CAN + let format2OldPin: Format2Pin + let format2NewPin: Format2Pin + do { + canData = try CAN.from(Data(can.utf8)) + format2OldPin = try Format2Pin(pincode: oldPin) + format2NewPin = try Format2Pin(pincode: newPin) + } catch { + callOnMainThread { + self.pState = .error(Error.invalidCanOrPinFormat) + } + return + } + + cancellable = NFCTagReaderSession.publisher(messages: messages) + .mapError { Error.cardError($0) as Swift.Error } + .flatMap { (session: NFCCardSession) -> AnyPublisher, Swift.Error> in + session.updateAlert(message: NSLocalizedString("nfc_txt_msg_secure_channel", comment: "")) + return session.card // swiftlint:disable:this trailing_closure + .openSecureSession(can: canData, writeTimeout: 0, readTimeout: 0) + .userMessage( + session: session, + message: NSLocalizedString("nfc_txt_msg_reset_withNewPin", comment: "") + ) + .changeReferenceDataSetNewPin( + oldPin: format2OldPin, + newPin: format2NewPin, + type: EgkFileSystem.Pin.mrpinHome, + dfSpecific: false + ) + + .map { _ in true } + .map(ViewState.value) + .handleEvents(receiveOutput: { state in + if let value = state.value, value == true { + session + .updateAlert(message: NSLocalizedString("nfc_txt_msg_reset_success", + comment: "")) + session.invalidateSession(with: nil) + } else { + session.invalidateSession( + with: state.error?.localizedDescription ?? NSLocalizedString( + "nfc_txt_msg_failure", + comment: "" + ) + ) + } + }) + .mapError { error in + session.invalidateSession(with: error.localizedDescription) + return error + } + .eraseToAnyPublisher() + } + .receive(on: DispatchQueue.main) + .sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.pState = .error(error) + } else { + self?.pState = .idle + } + self?.cancellable?.cancel() + }, + receiveValue: { [weak self] value in + self?.pState = value + } + ) + } +} + +extension Publisher where Output == HealthCardType, Self.Failure == Swift.Error { + func changeReferenceDataSetNewPin( + oldPin: Format2Pin, + newPin: Format2Pin, + type: EgkFileSystem.Pin, + dfSpecific: Bool + ) -> AnyPublisher { + flatMap { secureCard in + secureCard.changeReferenceDataSetNewPin( + old: oldPin, + new: newPin, + type: type, + dfSpecific: dfSpecific + ) + .tryMap { response in + if case ChangeReferenceDataResponse.success = response { + return secureCard + } + if case let ChangeReferenceDataResponse.wrongSecretWarning(retryCount: count) = response { + throw NFCChangeReferenceDataController.Error.wrongPin(retryCount: count) + } + if case ChangeReferenceDataResponse.commandBlocked = response { + throw NFCChangeReferenceDataController.Error.commandBlocked + } + // else + throw NFCChangeReferenceDataController.Error.otherError + } + } + .eraseToAnyPublisher() + } +} diff --git a/Sources/NFCDemo/NFC/NFCLoginController.swift b/Sources/NFCDemo/NFC/NFCLoginController.swift index e2e279a..26625df 100644 --- a/Sources/NFCDemo/NFC/NFCLoginController.swift +++ b/Sources/NFCDemo/NFC/NFCLoginController.swift @@ -31,6 +31,8 @@ public class NFCLoginController: LoginController { case wrongPin(retryCount: Int) case signatureFailure(ResponseStatus) case invalidAlgorithm(PSOAlgorithm) + case passwordBlocked + case verifyPinResponse public var errorDescription: String? { switch self { @@ -44,6 +46,10 @@ public class NFCLoginController: LoginController { return "signatureFailure with response status code: \(status.code)" case let .invalidAlgorithm(algorithm): return "SmartCard is not using a brainpoolP256r1 algorithm for signing. Uses: \(algorithm)" + case .passwordBlocked: + return "PIN usage counter exhausted" + case .verifyPinResponse: + return "Unexpected VerifyPinResponse" } } } @@ -128,6 +134,8 @@ public class NFCLoginController: LoginController { .sink(receiveCompletion: { [weak self] completion in if case let .failure(error) = completion { self?.pState = .error(error) + } else { + self?.pState = .idle } self?.cancellable?.cancel() }, receiveValue: { [weak self] value in @@ -153,9 +161,15 @@ extension Publisher where Output == HealthCardType, Self.Failure == Swift.Error flatMap { secureCard in secureCard.verify(pin: pin, type: type) .tryMap { response in - if case let VerifyPinResponse.failed(retryCount: count) = response { + if case let VerifyPinResponse.wrongSecretWarning(retryCount: count) = response { throw NFCLoginController.Error.wrongPin(retryCount: count) } + if case VerifyPinResponse.passwordBlocked = response { + throw NFCLoginController.Error.passwordBlocked + } + if response != VerifyPinResponse.success { + throw NFCLoginController.Error.verifyPinResponse + } return secureCard } }.eraseToAnyPublisher() diff --git a/Sources/NFCDemo/NFC/NFCResetRetryCounterController.swift b/Sources/NFCDemo/NFC/NFCResetRetryCounterController.swift index e83439d..09fca23 100644 --- a/Sources/NFCDemo/NFC/NFCResetRetryCounterController.swift +++ b/Sources/NFCDemo/NFC/NFCResetRetryCounterController.swift @@ -129,14 +129,19 @@ public class NFCResetRetryCounterController: ResetRetryCounter { .eraseToAnyPublisher() } .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { [weak self] completion in - if case let .failure(error) = completion { - self?.pState = .error(error) + .sink( + receiveCompletion: { [weak self] completion in + if case let .failure(error) = completion { + self?.pState = .error(error) + } else { + self?.pState = .idle + } + self?.cancellable?.cancel() + }, + receiveValue: { [weak self] value in + self?.pState = value } - self?.cancellable?.cancel() - }, receiveValue: { [weak self] value in - self?.pState = value - }) + ) } // swiftlint:disable:next function_body_length @@ -201,6 +206,8 @@ public class NFCResetRetryCounterController: ResetRetryCounter { .sink(receiveCompletion: { [weak self] completion in if case let .failure(error) = completion { self?.pState = .error(error) + } else { + self?.pState = .idle } self?.cancellable?.cancel() }, receiveValue: { [weak self] value in diff --git a/Sources/NFCDemo/Resources/Base.lproj/Localizable.strings b/Sources/NFCDemo/Resources/Base.lproj/Localizable.strings index d834a59..f7324a3 100644 --- a/Sources/NFCDemo/Resources/Base.lproj/Localizable.strings +++ b/Sources/NFCDemo/Resources/Base.lproj/Localizable.strings @@ -28,7 +28,8 @@ /* RESET */ -"reset_txt_title" = "Change PIN"; +"reset_txt_title" = "Unlock card"; +"reset_txt_title_with_new_pin" = "Change PIN"; "reset_txt_intro" = "Authorize yourself with your PUK and enter your new PIN."; "reset_edt_enter_puk" = "PUK-code"; "reset_edt_enter_new_pin" = "New PIN"; @@ -36,6 +37,15 @@ "reset_txt_help" = "I don't have a PUK"; "reset_txt_explanation" = "A PUK code will be mailed to you by your statutory health insurer shortly after you've received your health card."; +/* + CHANGE PIN +*/ +"change_txt_title" = "Change PIN"; +"change_txt_intro" = "Enter your current PIN and a new PIN."; +"change_edt_enter_old_pin" = "Current PIN"; +"change_edt_enter_new_pin" = "New PIN"; +"change_btn_next" = "Next"; + /* StartNFC */ diff --git a/Sources/NFCDemo/Resources/Info.plist b/Sources/NFCDemo/Resources/Info.plist index 4fb88d2..b044ac7 100644 --- a/Sources/NFCDemo/Resources/Info.plist +++ b/Sources/NFCDemo/Resources/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.2.0 + 1.2.1 CFBundleVersion - 19 + 20 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/Sources/NFCDemo/Resources/de.lproj/Localizable.strings b/Sources/NFCDemo/Resources/de.lproj/Localizable.strings index 660e8c0..e65fd59 100644 --- a/Sources/NFCDemo/Resources/de.lproj/Localizable.strings +++ b/Sources/NFCDemo/Resources/de.lproj/Localizable.strings @@ -28,14 +28,24 @@ /* RESET */ -"reset_txt_title" = "Wunsch-PIN"; -"reset_txt_intro" = "Authorisieren Sie sich mit Ihrer PUK und geben sie ihre neue Wunsch-PIN ein."; +"reset_txt_title" = "Karte entsperren"; +"reset_txt_title_with_new_pin" = "Wunsch-PIN"; +"reset_txt_intro" = "Authorisieren Sie sich mit Ihrer PUK und geben Sie Ihre neue Wunsch-PIN ein."; "reset_edt_enter_puk" = "PUK-code"; "reset_edt_enter_new_pin" = "Wunsch-PIN"; "reset_btn_next" = "Weiter"; "reset_txt_help" = "Ich habe keine PUK"; "reset_txt_explanation" = "Eine PUK erhalten Sie von ihrer Krankenkasse, nachdem Sie ihre NFC-fähige Gesundheitskarte erhalten haben."; +/* + CHANGE PIN +*/ +"change_txt_title" = "Wunsch-PIN"; +"change_txt_intro" = "Geben Sie Ihre bisherige und Ihre neue Wunsch-PIN ein."; +"change_edt_enter_old_pin" = "Aktuelle PIN"; +"change_edt_enter_new_pin" = "Neue Wunsch-PIN"; +"change_btn_next" = "Weiter"; + /* StartNFC */ diff --git a/Sources/NFCDemo/Screens/Registration/ChangeReferenceDataSetNewPINView.swift b/Sources/NFCDemo/Screens/Registration/ChangeReferenceDataSetNewPINView.swift new file mode 100644 index 0000000..1ef8b70 --- /dev/null +++ b/Sources/NFCDemo/Screens/Registration/ChangeReferenceDataSetNewPINView.swift @@ -0,0 +1,103 @@ +// +// Copyright (c) 2022 gematik GmbH +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation +import SwiftUI + +struct ChangeReferenceDataSetNewPINView: View { + @Environment(\.presentationMode) var presentationMode: Binding + var can: String = "" + @State var showStartNFCView = false + @State var oldPin: String = "" + @State var newPin: String = "" + @ObservedObject var keyboardHeight = KeyboardHeight() + var buttonEnabled: Bool { + newPin.count >= 5 && newPin.count <= 6 && oldPin.count > 5 && oldPin.count <= 6 + } + + var body: some View { + ScrollView(.vertical, showsIndicators: false) { + Section(header: HeaderView()) { + VStack(spacing: 20) { + SecureField("change_edt_enter_old_pin", text: $oldPin) + .keyboardType(.numberPad) + .padding() + .overlay(RoundedRectangle(cornerRadius: 8).stroke(Colors.grayBorder, lineWidth: 1)) + .accessibility(identifier: "change_edt_enter_old_pin") + + SecureField("change_edt_enter_new_pin", text: $newPin) + .keyboardType(.numberPad) + .padding() + .overlay(RoundedRectangle(cornerRadius: 8).stroke(Colors.grayBorder, lineWidth: 1)) + .accessibility(identifier: "change_edt_enter_new_pin") + + Button { + UIApplication.shared.dismissKeyboard() + showStartNFCView = true + } label: { + GTextButton(label: "change_btn_next", enabled: buttonEnabled) + .accessibility(identifier: "change_btn_next") + .disabled(!buttonEnabled) + } + .disabled(!buttonEnabled) + } + .fixedSize(horizontal: false, vertical: true) + } + } + .padding(.horizontal) + .background(Color(.secondarySystemBackground).ignoresSafeArea()) + .padding(.bottom, keyboardHeight.height) + .edgesIgnoringSafeArea(.bottom) + .navigationTitle("change_txt_title") + .fullScreenCover( + isPresented: $showStartNFCView, + onDismiss: { showStartNFCView = false }, + content: { + NavigationView { + StartNFCView( + can: can, + puk: "", + oldPin: oldPin, + pin: newPin, + useCase: .changeReferenceDataSetNewPin + ) + } + } + ) + } + + struct HeaderView: View { + var body: some View { + HStack { + Text("change_txt_intro") + .font(.subheadline) + .accessibility(identifier: "change_txt_intro") + Spacer() + }.padding(.vertical) + } + } +} + +#if DEBUG +struct ChangeReferenceDataSetNewPIN_Previews: PreviewProvider { + static var previews: some View { + ChangeReferenceDataSetNewPINView( + can: "1234" + ) + } +} +#endif diff --git a/Sources/NFCDemo/Screens/Registration/NFCChangeReferenceDataViewModel.swift b/Sources/NFCDemo/Screens/Registration/NFCChangeReferenceDataViewModel.swift new file mode 100644 index 0000000..15b1ccd --- /dev/null +++ b/Sources/NFCDemo/Screens/Registration/NFCChangeReferenceDataViewModel.swift @@ -0,0 +1,65 @@ +// +// Copyright (c) 2022 gematik GmbH +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine +import Foundation +import Helper +import NFCCardReaderProvider +import SwiftUI + +protocol ChangeReferenceData { + var state: Published>.Publisher { get } + + func changeReferenceDataSetNewPin(can: String, oldPin: String, newPin: String) + + func dismissError() +} + +class NFCChangeReferenceDataViewModel: ObservableObject { + @Environment(\.changeReferenceDataController) var changeReferenceDataController: ChangeReferenceData + @Published var state: ViewState = .idle + @Published var results: [ReadingResult] = [] + + private var disposables = Set() + + init(state: ViewState = .idle, results: [ReadingResult] = []) { + self.state = state + self.results = results + changeReferenceDataController.state + .dropFirst() + .sink { [weak self] viewState in + self?.state = viewState + + guard !viewState.isLoading, !viewState.isIdle else { + return + } + + let result = ReadingResult(result: viewState, + commands: CommandLogger.commands) + self?.results.append(result) + CommandLogger.commands = [] + } + .store(in: &disposables) + } + + func changeReferenceDataSetNewPin(can: String, oldPin: String, newPin: String) { + changeReferenceDataController.changeReferenceDataSetNewPin(can: can, oldPin: oldPin, newPin: newPin) + } + + func dismissError() { + changeReferenceDataController.dismissError() + } +} diff --git a/Sources/NFCDemo/Screens/Registration/RegisterCANView.swift b/Sources/NFCDemo/Screens/Registration/RegisterCANView.swift index b9471cc..c2e0aae 100644 --- a/Sources/NFCDemo/Screens/Registration/RegisterCANView.swift +++ b/Sources/NFCDemo/Screens/Registration/RegisterCANView.swift @@ -62,7 +62,7 @@ struct RegisterCANView: View { } .disabled(!buttonEnabled) - NavigationLink(destination: ResetRetryCounterWithNewPINView(can: storedCan)) { + NavigationLink(destination: ChangeReferenceDataSetNewPINView(can: storedCan)) { GTextButton(label: "can_btn_next_reset_pin_with_new_pin", enabled: buttonEnabled) .accessibility(identifier: "can_btn_next_reset_pin_with_new_pin") } diff --git a/Sources/NFCDemo/Screens/Registration/RegisterPINView.swift b/Sources/NFCDemo/Screens/Registration/RegisterPINView.swift index f9bb4e4..6bb8a58 100644 --- a/Sources/NFCDemo/Screens/Registration/RegisterPINView.swift +++ b/Sources/NFCDemo/Screens/Registration/RegisterPINView.swift @@ -65,7 +65,7 @@ struct RegisterPINView: View { showStartNFCView = false }, content: { NavigationView { - StartNFCView(can: can, puk: "", pin: storedPin, useCase: .login) + StartNFCView(can: can, puk: "", oldPin: "", pin: storedPin, useCase: .login) } }) } diff --git a/Sources/NFCDemo/Screens/Registration/ResetRetryCounterView.swift b/Sources/NFCDemo/Screens/Registration/ResetRetryCounterView.swift index a24bd9d..6740126 100644 --- a/Sources/NFCDemo/Screens/Registration/ResetRetryCounterView.swift +++ b/Sources/NFCDemo/Screens/Registration/ResetRetryCounterView.swift @@ -59,14 +59,15 @@ struct ResetRetryCounterView: View { .background(Color(.secondarySystemBackground).ignoresSafeArea()) .padding(.bottom, self.keyboardHeight.height) .edgesIgnoringSafeArea(.bottom) - .navigationTitle("Karte entsperren") + .navigationTitle("reset_txt_title") .fullScreenCover( isPresented: $showStartNFCView, onDismiss: { showStartNFCView = false - }, content: { + }, + content: { NavigationView { - StartNFCView(can: can, puk: puk, pin: "", useCase: .resetRetryCounter) + StartNFCView(can: can, puk: puk, oldPin: "", pin: "", useCase: .resetRetryCounter) } } ) diff --git a/Sources/NFCDemo/Screens/Registration/ResetRetryCounterWithNewPINView.swift b/Sources/NFCDemo/Screens/Registration/ResetRetryCounterWithNewPINView.swift index d96691f..7fa7223 100644 --- a/Sources/NFCDemo/Screens/Registration/ResetRetryCounterWithNewPINView.swift +++ b/Sources/NFCDemo/Screens/Registration/ResetRetryCounterWithNewPINView.swift @@ -66,13 +66,19 @@ struct ResetRetryCounterWithNewPINView: View { .background(Color(.secondarySystemBackground).ignoresSafeArea()) .padding(.bottom, self.keyboardHeight.height) .edgesIgnoringSafeArea(.bottom) - .navigationTitle("reset_txt_title") + .navigationTitle("reset_txt_title_with_new_pin") .fullScreenCover(isPresented: $showStartNFCView, onDismiss: { showStartNFCView = false }, content: { NavigationView { - StartNFCView(can: can, puk: puk, pin: newPin, useCase: .resetRetryCounterWithNewPin) + StartNFCView( + can: can, + puk: puk, + oldPin: "", + pin: newPin, + useCase: .resetRetryCounterWithNewPin + ) } }) } diff --git a/Sources/NFCDemo/Screens/Registration/StartNFCView.swift b/Sources/NFCDemo/Screens/Registration/StartNFCView.swift index 6f36aab..4e8261c 100644 --- a/Sources/NFCDemo/Screens/Registration/StartNFCView.swift +++ b/Sources/NFCDemo/Screens/Registration/StartNFCView.swift @@ -21,11 +21,13 @@ import SwiftUI struct StartNFCView: View { let can: String let puk: String + let oldPin: String let pin: String let useCase: UseCase @Environment(\.presentationMode) var presentationMode: Binding @StateObject var loginState = NFCLoginViewModel() @StateObject var resetRetryCounterState = NFCResetRetryCounterViewModel() + @StateObject var changeReferenceDataState = NFCChangeReferenceDataViewModel() @State var error: Swift.Error? @State private var showAlert = false @State var loading = false @@ -103,6 +105,8 @@ struct StartNFCView: View { case .resetRetryCounter: resetRetryCounterState.resetRetryCounter(can: can, puk: puk) case .resetRetryCounterWithNewPin: resetRetryCounterState .resetRetryCounterWithNewPin(can: can, puk: puk, newPin: pin) + case .changeReferenceDataSetNewPin: changeReferenceDataState + .changeReferenceDataSetNewPin(can: can, oldPin: oldPin, newPin: pin) } } label: { @@ -164,7 +168,8 @@ struct StartNFCView: View { enum UseCase { case login case resetRetryCounter - case resetRetryCounterWithNewPin + case resetRetryCounterWithNewPin // do not use this for solely setting a new PIN value + case changeReferenceDataSetNewPin } } @@ -174,6 +179,7 @@ struct StartNFCView_Previews: PreviewProvider { StartNFCView( can: "123456", puk: "", + oldPin: "123456", pin: "123456", useCase: .login ) diff --git a/Tests/NFCDemoTests/StartNFCViewSnapshotTests.swift b/Tests/NFCDemoTests/StartNFCViewSnapshotTests.swift index 8a63de6..1f9c20d 100644 --- a/Tests/NFCDemoTests/StartNFCViewSnapshotTests.swift +++ b/Tests/NFCDemoTests/StartNFCViewSnapshotTests.swift @@ -21,8 +21,10 @@ import XCTest class StartNFCViewSnapshotTests: XCTestCase { func testStartNFCViewSnapshotTests() throws { - let sut = NavigationView { StartNFCView(can: "123456", puk: "12345678", pin: "123", useCase: .login) } - .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) + let sut = NavigationView { + StartNFCView(can: "123456", puk: "12345678", oldPin: "123456", pin: "654321", useCase: .login) + } + .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) assertSnapshots(matching: sut, as: snapshotModi()) } } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 49bb0a5..b4172b2 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -66,7 +66,7 @@ lane :test_mac do mac_schemes.each do |scheme| scan( scheme: scheme, - destination: "platform=macOS,arch=x86_64" + destination: "platform=macOS,arch=x86_64" ) end end diff --git a/fastlane/README.md b/fastlane/README.md index b48b85f..7a4e798 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -1,70 +1,94 @@ fastlane documentation -================ +---- + # Installation Make sure you have the latest version of the Xcode command line tools installed: -``` +```sh xcode-select --install ``` -Install _fastlane_ using -``` -[sudo] gem install fastlane -NV -``` -or alternatively using `brew install fastlane` +For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) # Available Actions + ### carthage_resolve_dependencies + +```sh +[bundle exec] fastlane carthage_resolve_dependencies ``` -fastlane carthage_resolve_dependencies -``` + Lane that resolves the project dependencies using Carthage. + ### build_mac + +```sh +[bundle exec] fastlane build_mac ``` -fastlane build_mac -``` + Build the project schemes for macOS + ### test_mac + +```sh +[bundle exec] fastlane test_mac ``` -fastlane test_mac -``` + Build and test (scan) the project schemes for macOS CI builds should run this lane on every commit + ### build_ios + +```sh +[bundle exec] fastlane build_ios ``` -fastlane build_ios -``` + Build the project for iOS + ### test_ios + +```sh +[bundle exec] fastlane test_ios ``` -fastlane test_ios -``` + Build and test (scan) the project for iOS CI builds should run this lane on every commit + ### build_all + +```sh +[bundle exec] fastlane build_all ``` -fastlane build_all -``` + Build the project for macOS and iOS by calling `build_mac` and `build_ios` + ### test_all + +```sh +[bundle exec] fastlane test_all ``` -fastlane test_all -``` + Build and test (scan) the project for macOS and iOS by calling `test_mac` and `test_ios` + ### generate_documentation + +```sh +[bundle exec] fastlane generate_documentation ``` -fastlane generate_documentation -``` + Lane that (auto) genarates API documentation from inline comments. + ### static_code_analysis + +```sh +[bundle exec] fastlane static_code_analysis ``` -fastlane static_code_analysis -``` + Lane that runs the static code analyzer(s) for the project. CI builds should run this lane on every commit @@ -72,10 +96,13 @@ CI builds should run this lane on every commit Currently swiftlint is used as static analyzer + ### setup + +```sh +[bundle exec] fastlane setup ``` -fastlane setup -``` + Lane that sets up the SPM/Carthage dependencies and xcodeproj. @@ -87,10 +114,13 @@ fastlane setup configuration:Release ``` + ### cibuild + +```sh +[bundle exec] fastlane cibuild ``` -fastlane cibuild -``` + Lane that the ci build should invoke directly to do a complete build/test/analysis. This lane calls `setup`, `static_code_analysis`, @@ -106,10 +136,13 @@ fastlane cibuild ``` + ### build_ios_release + +```sh +[bundle exec] fastlane build_ios_release ``` -fastlane build_ios_release -``` + Build and sign the iOS NFCDemo App for Appstore Distribution @@ -121,10 +154,13 @@ fastlane build_ios_release configuration:Release ``` + ### bump_version + +```sh +[bundle exec] fastlane bump_version ``` -fastlane bump_version -``` + Bump the buildnumber with agvtool @@ -136,10 +172,13 @@ fastlane bump_version ``` + ### publish + +```sh +[bundle exec] fastlane publish ``` -fastlane publish -``` + Build, sign and upload the iOS NFCDemo to Testflight Note: The buildnumber is automatically increased and pushed back to remote git. @@ -159,5 +198,7 @@ fastlane publish ---- This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. -More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). -The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). + +More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). + +The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).