From bea194adaf1881be883c125284086cfe6ac7772a Mon Sep 17 00:00:00 2001 From: Jens Utbult Date: Thu, 7 Mar 2024 10:22:22 +0100 Subject: [PATCH] Added feature tests for touch and sha512 to OATHSession. Also added corresponding full stack tests. --- FullStackTests/Tests/OATHFullStackTests.swift | 38 +++++++++++++++++++ YubiKit/YubiKit/OATH/OATHSession.swift | 10 ++++- YubiKit/YubiKit/OATH/OATHSessionFeature.swift | 8 +++- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/FullStackTests/Tests/OATHFullStackTests.swift b/FullStackTests/Tests/OATHFullStackTests.swift index f48d7ef..5452908 100644 --- a/FullStackTests/Tests/OATHFullStackTests.swift +++ b/FullStackTests/Tests/OATHFullStackTests.swift @@ -172,6 +172,44 @@ class OATHFullStackTests: XCTestCase { } } + func testSHA512Feature() throws { + runOATHTest(populated: false) { session in + let template = OATHSession.CredentialTemplate(type: .TOTP(), algorithm: .SHA512, secret: "abba2".base32DecodedData!, issuer: "SHA-512", name: "FeatureTest") + do { + try await session.addCredential(template: template) + guard let credential = try await session.listCredentials().first else { XCTFail("Failed adding SHA512 credential."); return } + XCTAssertEqual(credential.hashAlgorithm!, .SHA512) + XCTAssertEqual(String(data: credential.id, encoding: .utf8), template.identifier) + } catch { + guard let error = error as? SessionError, error == .notSupported else { XCTFail("Unexpected error: \(error)"); return } + print("⚠️ Skip testSHA512Feature()") + } + } + } + + func testTouchFeature() throws { + runOATHTest(populated: false) { session in + do { + let touchTemplate = OATHSession.CredentialTemplate(type: .TOTP(), algorithm: .SHA256, secret: "abba2".base32DecodedData!, issuer: "Touch", name: "FeatureTest", requiresTouch: true) + try await session.addCredential(template: touchTemplate) + guard let touchCredential = try await session.calculateCodes().first else { XCTFail("Failed adding touch required credential."); return } + XCTAssertEqual(String(data: touchCredential.0.id, encoding: .utf8), touchTemplate.identifier) + XCTAssertTrue(touchCredential.0.requiresTouch) + XCTAssertNil(touchCredential.1) + try await session.deleteCredential(touchCredential.0) + let noTouchTemplate = OATHSession.CredentialTemplate(type: .TOTP(), algorithm: .SHA256, secret: "abba2".base32DecodedData!, issuer: "Touch", name: "FeatureTest", requiresTouch: false) + try await session.addCredential(template: noTouchTemplate) + guard let noTouchCredential = try await session.calculateCodes().first else { XCTFail("Failed adding no touch required credential."); return } + XCTAssertEqual(String(data: noTouchCredential.0.id, encoding: .utf8), noTouchTemplate.identifier) + XCTAssertNotNil(noTouchCredential.1) + XCTAssertFalse(noTouchCredential.0.requiresTouch) + } catch { + guard let error = error as? SessionError, error == .notSupported else { XCTFail("Unexpected error: \(error)"); return } + print("⚠️ Skip testTouchFeature()") + } + } + } + func testDeleteAccessKey() throws { runOATHTest(password: "password") { session in do { diff --git a/YubiKit/YubiKit/OATH/OATHSession.swift b/YubiKit/YubiKit/OATH/OATHSession.swift index c0c4644..02a43df 100644 --- a/YubiKit/YubiKit/OATH/OATHSession.swift +++ b/YubiKit/YubiKit/OATH/OATHSession.swift @@ -126,13 +126,19 @@ public final actor OATHSession: Session, InternalSession { /// The Credential ID (see ``OATHSession/CredentialTemplate/identifier``) must be unique to the YubiKey, or the /// existing Credential with the same ID will be overwritten. /// - /// Setting requireTouch requires support for FEATURE_TOUCH, available on YubiKey 4.2 or later. - /// Using SHA-512 requires support for FEATURE_SHA512, available on YubiKey 4.3.1 or later. + /// Setting requireTouch requires support for touch, available on YubiKey 4.2 or later. + /// Using SHA-512 requires support for SHA-512, available on YubiKey 4.3.1 or later. /// - Parameter template: The template describing the credential. /// - Returns: The newly added credential. @discardableResult public func addCredential(template: CredentialTemplate) async throws -> Credential { Logger.oath.debug("\(String(describing: self).lastComponent), \(#function)") + if template.algorithm == .SHA512 { + guard self.supports(OATHSessionFeature.sha512) else { throw SessionError.notSupported } + } + if template.requiresTouch { + guard self.supports(OATHSessionFeature.touch) else { throw SessionError.notSupported } + } guard let connection = _connection else { throw SessionError.noConnection } guard let nameData = template.identifier.data(using: .utf8) else { throw OATHSessionError.unexpectedData } let nameTlv = TKBERTLVRecord(tag: 0x71, value: nameData) diff --git a/YubiKit/YubiKit/OATH/OATHSessionFeature.swift b/YubiKit/YubiKit/OATH/OATHSessionFeature.swift index c8abc90..527a331 100644 --- a/YubiKit/YubiKit/OATH/OATHSessionFeature.swift +++ b/YubiKit/YubiKit/OATH/OATHSessionFeature.swift @@ -16,12 +16,16 @@ import Foundation public enum OATHSessionFeature: SessionFeature { - case rename - + case rename, touch, sha512 + public func isSupported(by version: Version) -> Bool { switch self { case .rename: return version >= Version(withString: "5.3.0")! + case .touch: + return version >= Version(withString: "4.2.0")! + case .sha512: + return version >= Version(withString: "4.3.1")! } } }