Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,5 @@ let package = Package(
.product(name: "SwiftECC", package: "SwiftECC")
]
)
],
swiftLanguageModes: [.v5]
]
)
6 changes: 3 additions & 3 deletions Sources/nfclib/CardActions/CardCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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
}
2 changes: 0 additions & 2 deletions Sources/nfclib/CardActions/CardReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 4 additions & 12 deletions Sources/nfclib/CardActions/CardReaderNFC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import OSLog
import CoreNFC
import CommonCrypto
import CryptoTokenKit
internal import SwiftECC
internal import BigInt
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -383,7 +382,7 @@ class CardReaderNFC: CardReader {
Point(x3Val, (AVal * UVal).mod(pVal))
}

static private func decryptNonce<T: AES.DataType>(CAN: String, encryptedNonce: T) throws -> Bytes {
static private func decryptNonce<T: DataType>(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)
Expand All @@ -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 {
Expand Down
10 changes: 5 additions & 5 deletions Sources/nfclib/CardActions/Idemia.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions Sources/nfclib/CardActions/Thales.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -147,20 +147,20 @@ 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 }

_ = try await select(file: Thales.kAID)
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)
Expand Down
1 change: 0 additions & 1 deletion Sources/nfclib/Utilities/AES.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
29 changes: 29 additions & 0 deletions Sources/nfclib/Utilities/SHA.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
8 changes: 8 additions & 0 deletions Sources/nfclib/Utilities/Types.swift
Original file line number Diff line number Diff line change
@@ -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
4 changes: 0 additions & 4 deletions Sources/nfclib/Utilities/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 6 additions & 47 deletions Sources/nfclib/WebEID/OperationAuthenticateWithWebEID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import Security
public enum AuthenticateWithWebEidError: Error {
case failedToReadPublicKey
case failedToDetermineAlgorithm
case failedToHashData
case failedToMapAlgorithm
case failedCertificateExpired
case failedCertificateNotYetValid
Expand Down Expand Up @@ -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) }
Expand All @@ -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()
)
Expand All @@ -154,38 +140,11 @@ 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) {
guard session === self.session else { return }
self.session = nil
finish(.failure(IdCardInternalError.mapSessionInvalidation(error)))
}

}
41 changes: 0 additions & 41 deletions Sources/nfclib/WebEID/WebEIDHash.swift

This file was deleted.

6 changes: 3 additions & 3 deletions Sources/nfclib/WebEID/WebEidData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,7 +24,7 @@ public final class WebEidData: Sendable {
====
unverifiedCertificate: \(unverifiedCertificate)
====
algorithm: \(algorithm)
algorithm: \(algorithm.rawValue)
====
signature: \(signature)
====
Expand Down
37 changes: 37 additions & 0 deletions Sources/nfclib/WebEID/WebEidSignatureAlgorithm.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Loading