diff --git a/Package.swift b/Package.swift index 69432d4..9dee966 100644 --- a/Package.swift +++ b/Package.swift @@ -22,6 +22,5 @@ let package = Package( .product(name: "SwiftECC", package: "SwiftECC") ] ) - ], - swiftLanguageModes: [.v5] + ] ) diff --git a/Sources/nfclib/CardActions/CardCommands.swift b/Sources/nfclib/CardActions/CardCommands.swift index 7308f03..ee7baff 100644 --- a/Sources/nfclib/CardActions/CardCommands.swift +++ b/Sources/nfclib/CardActions/CardCommands.swift @@ -125,7 +125,7 @@ public protocol CardCommands: Sendable { * - Throws: An error if the operation fails. * - Returns: The authentication response as `Data`. */ - func authenticate(for hash: Data, withPin1 pin1: SecureData) async throws -> Data + func authenticate(for hash: any DataType, withPin1 pin1: SecureData) async throws -> Data /** * Calculates a digital signature for the given hash. @@ -136,7 +136,7 @@ public protocol CardCommands: Sendable { * - Throws: An error if the operation fails. * - Returns: The signature as `Data`. */ - func calculateSignature(for hash: Data, withPin2 pin2: SecureData) async throws -> Data + func calculateSignature(for hash: any DataType, withPin2 pin2: SecureData) async throws -> Data /** * Decrypts data using PIN 1. @@ -147,5 +147,5 @@ public protocol CardCommands: Sendable { * - Throws: An error if the operation fails. * - Returns: The decrypted data. */ - func decryptData(_ hash: Data, withPin1 pin1: SecureData) async throws -> Data + func decryptData(_ hash: any DataType, withPin1 pin1: SecureData) async throws -> Data } diff --git a/Sources/nfclib/CardActions/CardReader.swift b/Sources/nfclib/CardActions/CardReader.swift index f7ca6ff..7a660d4 100644 --- a/Sources/nfclib/CardActions/CardReader.swift +++ b/Sources/nfclib/CardActions/CardReader.swift @@ -3,8 +3,6 @@ import Foundation -typealias Bytes = [UInt8] - protocol CardReader: Sendable { /** * Sends an APDU (Application Protocol Data Unit) command to the smart card and retrieves the response. diff --git a/Sources/nfclib/CardActions/CardReaderNFC.swift b/Sources/nfclib/CardActions/CardReaderNFC.swift index 50a81f6..65eb6ba 100644 --- a/Sources/nfclib/CardActions/CardReaderNFC.swift +++ b/Sources/nfclib/CardActions/CardReaderNFC.swift @@ -3,7 +3,6 @@ import OSLog import CoreNFC -import CommonCrypto import CryptoTokenKit internal import SwiftECC internal import BigInt @@ -308,8 +307,8 @@ class CardReaderNFC: CardReader { // MARK: - Utils // swiftlint:disable force_unwrapping static private func pseudoRandomNumberMappingAES( - sVal: any AES.DataType, - tVal: any AES.DataType, + sVal: any DataType, + tVal: any DataType, domain: Domain ) throws -> BInt { let lVal = sVal.count * 8 @@ -383,7 +382,7 @@ class CardReaderNFC: CardReader { Point(x3Val, (AVal * UVal).mod(pVal)) } - static private func decryptNonce(CAN: String, encryptedNonce: T) throws -> Bytes { + static private func decryptNonce(CAN: String, encryptedNonce: T) throws -> Bytes { let decryptionKey = KDF(key: Bytes(CAN.utf8), counter: 3) let cipher = AES.CBC(key: decryptionKey) return try cipher.decrypt(encryptedNonce) @@ -392,14 +391,7 @@ class CardReaderNFC: CardReader { static private func KDF(key: Bytes, counter: UInt8) -> Bytes { var keydata = key + Bytes(repeating: 0x00, count: 4) keydata[keydata.count - 1] = counter - return SHA256(data: keydata) - } - - static private func SHA256(data: Bytes) -> Bytes { - Bytes(unsafeUninitializedCapacity: Int(CC_SHA256_DIGEST_LENGTH)) { buffer, initializedCount in - CC_SHA256(data, CC_LONG(data.count), buffer.baseAddress) - initializedCount = Int(CC_SHA256_DIGEST_LENGTH) - } + return sha(hashLength: .bits256, data: keydata) } static private func random(count: Int) throws -> Data { diff --git a/Sources/nfclib/CardActions/Idemia.swift b/Sources/nfclib/CardActions/Idemia.swift index 89c1c06..6613459 100644 --- a/Sources/nfclib/CardActions/Idemia.swift +++ b/Sources/nfclib/CardActions/Idemia.swift @@ -132,25 +132,25 @@ final class Idemia: CardCommandsInternal { // MARK: - Authentication & Signing - func authenticate(for hash: Data, withPin1 pin1: SecureData) async throws -> Data { + func authenticate(for hash: any DataType, withPin1 pin1: SecureData) async throws -> Data { _ = try await select(file: kAIDOberthur) try await verifyCode(.pin1, code: pin1) try await setSecEnv(mode: 0xA4, algo: [0xFF, 0x20, 0x08, 0x00], keyRef: AUTHKEY) var paddedHash = Data(repeating: 0x00, count: max(48, hash.count) - hash.count) - paddedHash.append(hash) + paddedHash.append(contentsOf: hash) return try await reader.sendAPDU(ins: 0x88, data: paddedHash, leByte: 0x00) } - func calculateSignature(for hash: Data, withPin2 pin2: SecureData) async throws -> Data { + func calculateSignature(for hash: any DataType, withPin2 pin2: SecureData) async throws -> Data { _ = try await select(file: kAIDQSCD) try await verifyCode(.pin2, code: pin2) try await setSecEnv(mode: 0xB6, algo: [0xFF, 0x15, 0x08, 0x00], keyRef: SIGNKEY) var paddedHash = Data(repeating: 0x00, count: max(48, hash.count) - hash.count) - paddedHash.append(hash) + paddedHash.append(contentsOf: hash) return try await reader.sendAPDU(ins: 0x2A, p1Byte: 0x9E, p2Byte: 0x9A, data: paddedHash, leByte: 0x00) } - func decryptData(_ hash: Data, withPin1 pin1: SecureData) async throws -> Data { + func decryptData(_ hash: any DataType, withPin1 pin1: SecureData) async throws -> Data { _ = try await select(file: kAIDOberthur) try await verifyCode(.pin1, code: pin1) try await setSecEnv(mode: 0xB8, algo: [0xFF, 0x30, 0x04, 0x00], keyRef: AUTHKEY) diff --git a/Sources/nfclib/CardActions/Thales.swift b/Sources/nfclib/CardActions/Thales.swift index 08675a1..0e01e39 100644 --- a/Sources/nfclib/CardActions/Thales.swift +++ b/Sources/nfclib/CardActions/Thales.swift @@ -129,7 +129,7 @@ final class Thales: CardCommandsInternal { // MARK: - Authentication & Signing - private func sign(type: CodeType, pin: SecureData, keyRef: UInt8, hash: Data) async throws -> Data { + private func sign(type: CodeType, pin: SecureData, keyRef: UInt8, hash: any DataType) async throws -> Data { try await verifyCode(type, code: pin) try await setSecEnv(mode: 0xB6, algo: [0x24 + UInt8(hash.count)], keyRef: keyRef) _ = try await reader @@ -147,12 +147,12 @@ final class Thales: CardCommandsInternal { return try await reader.sendAPDU(ins: 0x2A, p1Byte: 0x9E, p2Byte: 0x9A, leByte: 0x00) } - func authenticate(for hash: Data, withPin1 pin1: SecureData) async throws -> Data { + func authenticate(for hash: any DataType, withPin1 pin1: SecureData) async throws -> Data { _ = try await select(file: Thales.kAID) return try await sign(type: .pin1, pin: pin1, keyRef: Thales.AUTHKEY, hash: hash) } - func calculateSignature(for hash: Data, withPin2 pin2: SecureData) async throws -> Data { + func calculateSignature(for hash: any DataType, withPin2 pin2: SecureData) async throws -> Data { let (_, pin2Changed) = try await readCodeTryCounterRecord(.pin2) guard pin2Changed else { throw IdCardInternalError.notActivated } @@ -160,7 +160,7 @@ final class Thales: CardCommandsInternal { return try await sign(type: .pin2, pin: pin2, keyRef: Thales.SIGNKEY, hash: hash) } - func decryptData(_ hash: Data, withPin1 pin1: SecureData) async throws -> Data { + func decryptData(_ hash: any DataType, withPin1 pin1: SecureData) async throws -> Data { _ = try await select(file: Thales.kAID) try await verifyCode(.pin1, code: pin1) try await setSecEnv(mode: 0xB8, keyRef: Thales.AUTHKEY) diff --git a/Sources/nfclib/Utilities/AES.swift b/Sources/nfclib/Utilities/AES.swift index f7bd417..c10bd1b 100644 --- a/Sources/nfclib/Utilities/AES.swift +++ b/Sources/nfclib/Utilities/AES.swift @@ -5,7 +5,6 @@ import CommonCrypto import Foundation class AES { - typealias DataType = DataProtocol & ContiguousBytes static let BlockSize: Int = kCCBlockSizeAES128 static let Zero = Bytes(repeating: 0x00, count: BlockSize) diff --git a/Sources/nfclib/Utilities/SHA.swift b/Sources/nfclib/Utilities/SHA.swift new file mode 100644 index 0000000..ca382c2 --- /dev/null +++ b/Sources/nfclib/Utilities/SHA.swift @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +import Foundation +import CommonCrypto + +enum HashLength { + case bits256 + case bits384 + case bits512 +} + +func sha(hashLength: HashLength, data: some DataType) -> Bytes { + let (digestLength, hashFunction) = switch hashLength { + case .bits256: + (Int(CC_SHA256_DIGEST_LENGTH), CC_SHA256) + case .bits384: + (Int(CC_SHA384_DIGEST_LENGTH), CC_SHA384) + case .bits512: + (Int(CC_SHA512_DIGEST_LENGTH), CC_SHA512) + } + + return Bytes(unsafeUninitializedCapacity: digestLength) { buffer, initializedCount in + data.withUnsafeBytes { dataBytes in + _ = hashFunction(dataBytes.baseAddress, CC_LONG(dataBytes.count), buffer.baseAddress) + } + initializedCount = digestLength + } +} diff --git a/Sources/nfclib/Utilities/Types.swift b/Sources/nfclib/Utilities/Types.swift new file mode 100644 index 0000000..6100c18 --- /dev/null +++ b/Sources/nfclib/Utilities/Types.swift @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +import Foundation + +typealias Bytes = [UInt8] + +public typealias DataType = DataProtocol & ContiguousBytes diff --git a/Sources/nfclib/Utilities/Utilities.swift b/Sources/nfclib/Utilities/Utilities.swift index 76f518d..3470e8f 100644 --- a/Sources/nfclib/Utilities/Utilities.swift +++ b/Sources/nfclib/Utilities/Utilities.swift @@ -4,10 +4,6 @@ import Foundation import Security -public let rsaAlgorithmName = "RSA" -public let ecAlgorithmName = "EC" -public let unknownAlgorithmName = "Unknown" - func convertBytesToX509Certificate(_ data: Data) throws -> SecCertificate { guard let certificate = SecCertificateCreateWithData(nil, data as CFData) else { throw CertificateConversionError.creationFailed diff --git a/Sources/nfclib/WebEID/OperationAuthenticateWithWebEID.swift b/Sources/nfclib/WebEID/OperationAuthenticateWithWebEID.swift index 1b63344..2dc1f79 100644 --- a/Sources/nfclib/WebEID/OperationAuthenticateWithWebEID.swift +++ b/Sources/nfclib/WebEID/OperationAuthenticateWithWebEID.swift @@ -8,7 +8,6 @@ import Security public enum AuthenticateWithWebEidError: Error { case failedToReadPublicKey case failedToDetermineAlgorithm - case failedToHashData case failedToMapAlgorithm case failedCertificateExpired case failedCertificateNotYetValid @@ -113,25 +112,12 @@ extension OperationAuthenticateWithWebEID: @MainActor NFCTagReaderSessionDelegat return } - guard let keyAlgorithmData = getAlgorithmNameTypeAndLength(from: publicKey) else { - let errorMessage = "Failed to read data" - finish(.failure(AuthenticateWithWebEidError.failedToDetermineAlgorithm)) - session.invalidate(errorMessage: errorMessage) - return - } + let algorithm = try WebEidSignatureAlgorithm.forAuthenticationKey(publicKey) + let hashLength = algorithm.hashLength - guard let hashLength = hashLengthFromInt(keyAlgorithmData.keyLength), - let originData = origin.data(using: .utf8), - let challengeData = challenge.data(using: .utf8), - let originHash = sha(hashLength: hashLength, data: originData), - let challengeHash = sha(hashLength: hashLength, data: challengeData), - let webEidHash = sha(hashLength: hashLength, data: originHash + challengeHash) - else { - let errorMessage = "Failed to read data" - finish(.failure(AuthenticateWithWebEidError.failedToHashData)) - session.invalidate(errorMessage: errorMessage) - return - } + let originHash = sha(hashLength: hashLength, data: Bytes(origin.utf8)) + let challengeHash = sha(hashLength: hashLength, data: Bytes(challenge.utf8)) + let webEidHash = sha(hashLength: hashLength, data: originHash + challengeHash) updateAlertMessage(step: 4) guard let pin1 else { throw IdCardInternalError.pinVerificationFailed(codeType: .pin1) } @@ -140,7 +126,7 @@ extension OperationAuthenticateWithWebEID: @MainActor NFCTagReaderSessionDelegat let webEidData = WebEidData( unverifiedCertificate: certBytes.base64EncodedString(), - algorithm: keyAlgorithmData.algorithm, + algorithm: algorithm, signature: authResult.base64EncodedString(), signingCertificate: signingCertificateBytes.base64EncodedString() ) @@ -154,32 +140,6 @@ extension OperationAuthenticateWithWebEID: @MainActor NFCTagReaderSessionDelegat } } - public func getAlgorithmNameTypeAndLength(from key: SecKey) -> (algorithm: String, keyLength: Int)? { - // Get the algorithm type from the key - if let algorithmAttributes = SecKeyCopyAttributes(key) as? [String: Any], let algorithmType = - algorithmAttributes[kSecAttrKeyType as String] as? String { - - // Get the algorithm name based on the type - var algorithmName = "" - var keyLength = 0 - - switch algorithmType { - case String(kSecAttrKeyTypeRSA): - algorithmName = rsaAlgorithmName - keyLength = (algorithmAttributes[kSecAttrKeySizeInBits as String] as? Int) ?? 0 - case String(kSecAttrKeyTypeEC): - algorithmName = ecAlgorithmName - keyLength = (algorithmAttributes[kSecAttrKeySizeInBits as String] as? Int) ?? 0 - default: - algorithmName = unknownAlgorithmName - } - - return (algorithm: algorithmName, keyLength: keyLength) - } else { - return nil - } - } - public func tagReaderSessionDidBecomeActive(_: NFCTagReaderSession) { } public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) { @@ -187,5 +147,4 @@ extension OperationAuthenticateWithWebEID: @MainActor NFCTagReaderSessionDelegat self.session = nil finish(.failure(IdCardInternalError.mapSessionInvalidation(error))) } - } diff --git a/Sources/nfclib/WebEID/WebEIDHash.swift b/Sources/nfclib/WebEID/WebEIDHash.swift deleted file mode 100644 index 0265387..0000000 --- a/Sources/nfclib/WebEID/WebEIDHash.swift +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-FileCopyrightText: Estonian Information System Authority -// SPDX-License-Identifier: LGPL-2.1-or-later - -import Foundation -import CommonCrypto - -enum HashLength: Int { - case bits256 = 256 - case bits384 = 384 - case bits512 = 512 -} - -func hashLengthFromInt(_ intValue: Int) -> HashLength? { - return HashLength(rawValue: intValue) -} - -func sha(hashLength: HashLength, data: Data) -> Data? { - let digestLength: Int - let hashFunction: (UnsafeRawPointer?, CC_LONG, UnsafeMutablePointer?) -> UnsafeMutablePointer? - - switch hashLength { - case .bits256: - digestLength = Int(CC_SHA256_DIGEST_LENGTH) - hashFunction = CC_SHA256 - case .bits384: - digestLength = Int(CC_SHA384_DIGEST_LENGTH) - hashFunction = CC_SHA384 - case .bits512: - digestLength = Int(CC_SHA512_DIGEST_LENGTH) - hashFunction = CC_SHA512 - } - - let hashBytes = Bytes(unsafeUninitializedCapacity: digestLength) { buffer, initializedCount in - data.withUnsafeBytes { dataBytes in - _ = hashFunction(dataBytes.baseAddress, CC_LONG(data.count), buffer.baseAddress) - } - initializedCount = digestLength - } - - return Data(hashBytes) -} diff --git a/Sources/nfclib/WebEID/WebEidData.swift b/Sources/nfclib/WebEID/WebEidData.swift index c992cc9..221f4bc 100644 --- a/Sources/nfclib/WebEID/WebEidData.swift +++ b/Sources/nfclib/WebEID/WebEidData.swift @@ -6,11 +6,11 @@ import Foundation public final class WebEidData: Sendable { public let unverifiedCertificate: String public let signingCertificate: String - public let algorithm: String + public let algorithm: WebEidSignatureAlgorithm public let signature: String init(unverifiedCertificate: String, - algorithm: String, + algorithm: WebEidSignatureAlgorithm, signature: String, signingCertificate: String) { self.unverifiedCertificate = unverifiedCertificate @@ -24,7 +24,7 @@ public final class WebEidData: Sendable { ==== unverifiedCertificate: \(unverifiedCertificate) ==== - algorithm: \(algorithm) + algorithm: \(algorithm.rawValue) ==== signature: \(signature) ==== diff --git a/Sources/nfclib/WebEID/WebEidSignatureAlgorithm.swift b/Sources/nfclib/WebEID/WebEidSignatureAlgorithm.swift new file mode 100644 index 0000000..a71590f --- /dev/null +++ b/Sources/nfclib/WebEID/WebEidSignatureAlgorithm.swift @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +import Foundation +import Security + +public enum WebEidSignatureAlgorithm: String, Sendable { + case es256 = "ES256" + case es384 = "ES384" + + var hashLength: HashLength { + switch self { + case .es256: .bits256 + case .es384: .bits384 + } + } + + static func forAuthenticationKey( + _ key: SecKey + ) throws(AuthenticateWithWebEidError) -> WebEidSignatureAlgorithm { + guard let attributes = SecKeyCopyAttributes(key) as? [String: Any], + let keyType = attributes[kSecAttrKeyType as String] as? String, + let keySizeInBits = attributes[kSecAttrKeySizeInBits as String] as? Int else { + throw .failedToDetermineAlgorithm + } + + guard keyType == String(kSecAttrKeyTypeECSECPrimeRandom) else { + throw .failedToMapAlgorithm + } + + switch keySizeInBits { + case 256: return .es256 + case 384: return .es384 + default: throw .failedToMapAlgorithm + } + } +}