Skip to content

Commit ed9f13a

Browse files
committed
Add MyEID NFC functionality
Refactor signing and decryption NFC operations
1 parent a310bad commit ed9f13a

37 files changed

Lines changed: 1395 additions & 434 deletions

Modules/CommonsLib/Sources/CommonsLib/Constants.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ public struct Constants {
100100
public struct File {
101101
public static let LibDigidocLog = "libdigidocpp.log"
102102
public static let LDAPCertsPem = "ldapCerts.pem"
103+
public static let nfcCANKey = "canKey.txt"
103104
}
104105

105106
public struct FileBaseName {

Modules/IdCardLib/Sources/IdCardLib/CardActions/CardCommandsInternal.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ extension CardCommandsInternal {
7474
return
7575
case 0x6A80: // New pin is invalid
7676
throw IdCardInternalError.invalidNewPin
77-
case 0x63C0, 0x6983: // Authentication method blocked
77+
case 0x63C0, 0x6983, 0x6984: // Authentication method blocked
7878
throw IdCardInternalError.pinVerificationFailed
7979
// For pin codes this means verification failed due to wrong pin
8080
case let uInt16 where (uInt16 & 0xFFF0) == 0x63C0:

Modules/IdCardLib/Sources/IdCardLib/CardActions/CardReaderNFC.swift

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -234,19 +234,38 @@ class CardReaderNFC: @unchecked CardReader, Loggable {
234234
}
235235
return (tlvEnc, tlvRes, tlvMac)
236236
}
237-
237+
// swiftlint:disable cyclomatic_complexity
238238
func transmit(_ apduData: Bytes) async throws -> (responseData: Bytes, sw: UInt16) {
239239
CardReaderNFC.logger().info("Plain >: \(apduData.toHex)")
240240
guard let apdu = NFCISO7816APDU(data: Data(apduData)) else {
241241
throw IdCardInternalError.invalidAPDU
242242
}
243243
_ = SSC.increment()
244-
let DO87 = try getDO87(apdu)
245-
let DO97 = try getDO97(apdu)
244+
let DO87: Data
245+
if let data = apdu.data, !data.isEmpty {
246+
let ivValue = try AES.CBC(key: ksEnc).encrypt(SSC)
247+
let encData = try AES.CBC(key: ksEnc, ivVal: ivValue).encrypt(data.addPadding())
248+
if apdu.instructionCode & 0x01 == 0 {
249+
DO87 = TLV(tag: 0x87, bytes: [0x01] + encData).data
250+
} else {
251+
DO87 = TLV(tag: 0x85, bytes: encData).data
252+
}
253+
} else {
254+
DO87 = Data()
255+
}
256+
let DO97: Data
257+
if apdu.expectedResponseLength > 0 {
258+
DO97 = TLV(
259+
tag: 0x97,
260+
bytes: [UInt8(apdu.expectedResponseLength == 256 ? 0 : apdu.expectedResponseLength)]
261+
).data
262+
} else {
263+
DO97 = Data()
264+
}
246265
let cmdHeader: Bytes = [apdu.instructionClass | 0x0C, apdu.instructionCode, apdu.p1Parameter, apdu.p2Parameter]
247-
let MValue = cmdHeader.addPadding() + DO87 + DO97
248-
let NValue = SSC + MValue
249-
let mac = try AES.CMAC(key: ksMac).authenticate(bytes: NValue.addPadding())
266+
let mVal = cmdHeader.addPadding() + DO87 + DO97
267+
let nVal = SSC + mVal
268+
let mac = try AES.CMAC(key: ksMac).authenticate(bytes: nVal.addPadding())
250269
let DO8E = TLV(tag: 0x8E, bytes: mac).data
251270
let send = DO87 + DO97 + DO8E
252271
let response = try await tag.sendCommand(
@@ -257,26 +276,39 @@ class CardReaderNFC: @unchecked CardReader, Loggable {
257276
data: send,
258277
leByte: 256
259278
)
260-
let (tlvEnc, tlvRes, tlvMac) = try getTLVs(response)
279+
var tlvEnc: TKTLVRecord?
280+
var tlvRes: TKTLVRecord?
281+
var tlvMac: TKTLVRecord?
282+
for tlv in TLV.sequenceOfRecords(from: response) ?? [] {
283+
switch tlv.tag {
284+
case 0x85, 0x87: tlvEnc = tlv
285+
case 0x99: tlvRes = tlv
286+
case 0x8E: tlvMac = tlv
287+
default: print("Unknown tag")
288+
}
289+
}
261290
guard let tlvRes else {
262291
throw IdCardInternalError.missingRESTag
263292
}
264293
guard let tlvMac else {
265294
throw IdCardInternalError.missingMACTag
266295
}
267-
let KValue = SSC.increment() + (tlvEnc?.data ?? Data()) + tlvRes.data
268-
if try Data(AES.CMAC(key: ksMac).authenticate(bytes: KValue.addPadding())) != tlvMac.value {
296+
let kVal = SSC.increment() + (tlvEnc?.data ?? Data()) + tlvRes.data
297+
if try Data(AES.CMAC(key: ksMac).authenticate(bytes: kVal.addPadding())) != tlvMac.value {
269298
throw IdCardInternalError.invalidMACValue
270299
}
271300
guard let tlvEnc else {
272301
CardReaderNFC.logger().info("Plain <: \(tlvRes.value.toHex)")
273302
return (.init(), UInt16(tlvRes.value[0], tlvRes.value[1]))
274303
}
275304
let ivValue = try AES.CBC(key: ksEnc).encrypt(SSC)
276-
let responseData = try (try AES.CBC(key: ksEnc, ivVal: ivValue).decrypt(tlvEnc.value[1...])).removePadding()
277-
CardReaderNFC.logger().info("Plain <: \(responseData.toHex) \(tlvRes.value.toHex)")
305+
let responseData = try (try AES.CBC(key: ksEnc, ivVal: ivValue)
306+
.decrypt(tlvEnc.tag == 0x85 ? tlvEnc.value : tlvEnc.value[1...]))
307+
.removePadding()
308+
CardReaderNFC.logger().debug("Plain <: \(responseData.toHex) \(tlvRes.value.toHex)")
278309
return (Bytes(responseData), UInt16(tlvRes.value[0], tlvRes.value[1]))
279310
}
311+
// swiftlint:enable cyclomatic_complexity
280312

281313
// MARK: - Utils
282314

Modules/IdCardLib/Sources/IdCardLib/CardActions/Thales.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ final class Thales: CardCommandsInternal {
115115
guard type != .puk else {
116116
throw IdCardInternalError.notSupportedCodeType
117117
}
118+
_ = try await select(file: Thales.kAID)
118119
try await unblockCode(type.pinRef, puk: puk, newCode: newCode)
119120
}
120121

Modules/IdCardLib/Sources/IdCardLib/Operations/OperationUnblockPin.swift

Lines changed: 0 additions & 104 deletions
This file was deleted.

RIADigiDoc.xcodeproj/project.pbxproj

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,19 +144,24 @@
144144
Domain/Model/EncryptionServerInfo.swift,
145145
Domain/Model/EncryptionServerOption.swift,
146146
Domain/Model/EncryptionServerOptionId.swift,
147-
Domain/Model/Error/DecryptError.swift,
147+
Domain/Model/Error/Encryption/EncryptedDataError.swift,
148148
Domain/Model/Error/FileOpeningErrors.swift,
149149
"Domain/Model/Error/My eID/MyEidCodeChangeError.swift",
150-
Domain/Model/Error/ReadCertAndSignError.swift,
150+
Domain/Model/Error/NFC/ChangePinError.swift,
151+
Domain/Model/Error/NFC/DecryptError.swift,
152+
Domain/Model/Error/NFC/ReadCertAndSignError.swift,
153+
Domain/Model/Error/NFC/UnblockPINError.swift,
151154
Domain/Model/FileItem.swift,
152155
Domain/Model/IdCard/IdCardData.swift,
153156
Domain/Model/IdCard/RetryCount.swift,
154157
Domain/Model/KeychainKey.swift,
155158
"Domain/Model/My eID/MyEidDocumentStatus.swift",
156159
"Domain/Model/My eID/MyEidPinCodeAction.swift",
157160
"Domain/Model/My eID/MyEidPinCodeStep.swift",
161+
Domain/Model/NFC/NFCCardData.swift,
158162
Domain/Model/Notification/ContainerNotificationType.swift,
159163
Domain/Model/SettingsMenuBottomSheetPages.swift,
164+
Domain/Model/Signing/ActionType.swift,
160165
Domain/Model/Signing/MobileId/MobileIdInputData.swift,
161166
Domain/Model/Signing/NFC/NFCInputData.swift,
162167
Domain/Model/Signing/SigningMethod.swift,
@@ -165,9 +170,14 @@
165170
Domain/Model/SupportedLanguage.swift,
166171
Domain/Model/SupportedTheme.swift,
167172
Domain/Model/ToastMessage.swift,
173+
Domain/NFC/NFCOperationBase.swift,
168174
Domain/NFC/NFCSessionStrings.swift,
175+
Domain/NFC/NFCSessionStringsUtil.swift,
176+
Domain/NFC/OperationChangePin.swift,
169177
Domain/NFC/OperationDecrypt.swift,
178+
Domain/NFC/OperationReadCardData.swift,
170179
Domain/NFC/OperationReadCertAndSign.swift,
180+
Domain/NFC/OperationUnblockPin.swift,
171181
Domain/Preferences/DataStore.swift,
172182
Domain/Preferences/DataStoreProtocol.swift,
173183
Domain/Preferences/KeychainStore.swift,
@@ -194,6 +204,7 @@
194204
UI/Theme/Theme.swift,
195205
Util/Certificate/CertificateUtil.swift,
196206
Util/Certificate/CertificateUtilProtocol.swift,
207+
Util/EncryptedData/EncryptedDataUtil.swift,
197208
Util/File/FileInspector.swift,
198209
Util/File/FileInspectorProtocol.swift,
199210
Util/Language/LanguageSettings.swift,

RIADigiDoc/DI/AppContainer.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,10 @@ extension Container {
507507
self { @MainActor in
508508
NFCViewModel(
509509
dataStore: self.dataStore(),
510-
userAgentUtil: self.userAgentUtil()
510+
userAgentUtil: self.userAgentUtil(),
511+
certificateUtil: self.certificateUtil(),
512+
sharedMyEidSession: self.sharedMyEidSession(),
513+
keychainStore: self.keychainStore()
511514
)
512515
}
513516
}
@@ -521,17 +524,31 @@ extension Container {
521524
}
522525
}
523526

527+
// swiftlint:disable closure_parameter_position
528+
// swiftlint:disable large_tuple
524529
@MainActor
525-
var myEidPinChangeViewModel: ParameterFactory<(MyEidPinCodeAction, CodeType, String), MyEidPinChangeViewModel> {
526-
self { @MainActor (pinAction: MyEidPinCodeAction, codeType: CodeType, personalCode: String
530+
var myEidPinChangeViewModel: ParameterFactory<(
531+
MyEidPinCodeAction,
532+
CodeType,
533+
String,
534+
ActionMethod
535+
), MyEidPinChangeViewModel> {
536+
self { @MainActor (
537+
pinAction: MyEidPinCodeAction,
538+
codeType: CodeType,
539+
personalCode: String,
540+
actionMethod: ActionMethod
527541
) -> MyEidPinChangeViewModel in
528542
MyEidPinChangeViewModel(
529543
pinAction: pinAction,
530544
codeType: codeType,
531545
personalCode: personalCode,
546+
actionMethod: actionMethod,
532547
idCardRepository: self.idCardRepository(),
533548
sharedMyEidSession: self.sharedMyEidSession()
534549
)
535550
}
536551
}
552+
// swiftlint:enable closure_parameter_position
553+
// swiftlint:enable large_tuple
537554
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2017 - 2025 Riigi Infosüsteemi Amet
3+
*
4+
* This library is free software; you can redistribute it and/or
5+
* modify it under the terms of the GNU Lesser General Public
6+
* License as published by the Free Software Foundation; either
7+
* version 2.1 of the License, or (at your option) any later version.
8+
*
9+
* This library is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public
15+
* License along with this library; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17+
*
18+
*/
19+
20+
import Foundation
21+
22+
public enum EncryptedDataError: Error, LocalizedError {
23+
case unableToLocateAppSupportDirectory
24+
case keyFileDoesNotExist
25+
case encryptionFailed
26+
case decryptionFailed
27+
28+
public var errorDescription: String? {
29+
switch self {
30+
case .unableToLocateAppSupportDirectory:
31+
return "Unable to locate Application Support directory"
32+
case .keyFileDoesNotExist:
33+
return "Encryption key file does not exist"
34+
case .encryptionFailed:
35+
return "Failed to encrypt data"
36+
case .decryptionFailed:
37+
return "Failed to decrypt data"
38+
}
39+
}
40+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2017 - 2025 Riigi Infosüsteemi Amet
3+
*
4+
* This library is free software; you can redistribute it and/or
5+
* modify it under the terms of the GNU Lesser General Public
6+
* License as published by the Free Software Foundation; either
7+
* version 2.1 of the License, or (at your option) any later version.
8+
*
9+
* This library is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
* Lesser General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Lesser General Public
15+
* License along with this library; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17+
*
18+
*/
19+
20+
import Foundation
21+
22+
public enum ChangePinError: Error {
23+
case missingRequiredParameter
24+
case cancelled
25+
case unknown(Error)
26+
}
27+
28+
extension ChangePinError: LocalizedError {
29+
public var errorDescription: String? {
30+
switch self {
31+
case .missingRequiredParameter:
32+
return "Missing required parameter"
33+
case .cancelled:
34+
return "Operation cancelled by user"
35+
case .unknown(let error):
36+
return "Unknown error: \(error.localizedDescription)"
37+
}
38+
}
39+
}
File renamed without changes.

0 commit comments

Comments
 (0)