diff --git a/Sources/WebAuthn/Helpers/ChallengeGenerator.swift b/Sources/WebAuthn/Helpers/ChallengeGenerator.swift index ecbaa69..a6bfb49 100644 --- a/Sources/WebAuthn/Helpers/ChallengeGenerator.swift +++ b/Sources/WebAuthn/Helpers/ChallengeGenerator.swift @@ -11,10 +11,18 @@ // //===----------------------------------------------------------------------===// +import Foundation + package struct ChallengeGenerator: Sendable { - var generate: @Sendable () -> [UInt8] + static let challengeSize: Int = 32 + + var generate: @Sendable (_ : [UInt8]) -> [UInt8] package static var live: Self { - .init(generate: { [UInt8].random(count: 32) }) + .init(generate: { challengeData in + var randomData = [UInt8].random(count: challengeSize) + randomData.append(contentsOf: challengeData) + return randomData + }) } } diff --git a/Sources/WebAuthn/WebAuthnManager.swift b/Sources/WebAuthn/WebAuthnManager.swift index 1da063b..1612caf 100644 --- a/Sources/WebAuthn/WebAuthnManager.swift +++ b/Sources/WebAuthn/WebAuthnManager.swift @@ -43,6 +43,22 @@ public struct WebAuthnManager: Sendable { self.configuration = configuration self.challengeGenerator = challengeGenerator } + + /// Extract challenge custom data from challenge + /// + /// - Parameters: + /// - challenge: The challenge to extract data from + /// + /// - Returns: The custom data part of the challenge + public func extractChallengeData(challenge : [UInt8]) -> [UInt8] { + if challenge.count <= ChallengeGenerator.challengeSize { + return [] + } + + let arrayslice = challenge.suffix(from: ChallengeGenerator.challengeSize) + return Array(arrayslice) + } + /// Generate a new set of registration data to be sent to the client. /// @@ -59,9 +75,10 @@ public struct WebAuthnManager: Sendable { user: PublicKeyCredentialUserEntity, timeout: Duration? = .seconds(5*60), attestation: AttestationConveyancePreference = .none, - publicKeyCredentialParameters: [PublicKeyCredentialParameters] = .supported + publicKeyCredentialParameters: [PublicKeyCredentialParameters] = .supported, + challengeData : [UInt8] = [] ) -> PublicKeyCredentialCreationOptions { - let challenge = challengeGenerator.generate() + let challenge = challengeGenerator.generate(challengeData) return PublicKeyCredentialCreationOptions( challenge: challenge, @@ -138,9 +155,10 @@ public struct WebAuthnManager: Sendable { public func beginAuthentication( timeout: Duration? = .seconds(60), allowCredentials: [PublicKeyCredentialDescriptor]? = nil, - userVerification: UserVerificationRequirement = .preferred + userVerification: UserVerificationRequirement = .preferred, + challengeData : [UInt8] = [] ) throws -> PublicKeyCredentialRequestOptions { - let challenge = challengeGenerator.generate() + let challenge = challengeGenerator.generate(challengeData) return PublicKeyCredentialRequestOptions( challenge: challenge, diff --git a/Tests/WebAuthnTests/Mocks/MockChallengeGenerator.swift b/Tests/WebAuthnTests/Mocks/MockChallengeGenerator.swift index a1aa4c1..8953a3b 100644 --- a/Tests/WebAuthnTests/Mocks/MockChallengeGenerator.swift +++ b/Tests/WebAuthnTests/Mocks/MockChallengeGenerator.swift @@ -15,6 +15,6 @@ extension ChallengeGenerator { static func mock(generate: [UInt8]) -> Self { - ChallengeGenerator(generate: { generate }) + ChallengeGenerator(generate: { _ in generate }) } } diff --git a/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift b/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift index 52f3752..665c5f5 100644 --- a/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift +++ b/Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift @@ -191,4 +191,5 @@ final class WebAuthnManagerAuthenticationTests: XCTestCase { requireUserVerification: requireUserVerification ) } + } diff --git a/Tests/WebAuthnTests/WebAuthnManagerChallengeTests.swift b/Tests/WebAuthnTests/WebAuthnManagerChallengeTests.swift new file mode 100644 index 0000000..13b7233 --- /dev/null +++ b/Tests/WebAuthnTests/WebAuthnManagerChallengeTests.swift @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift WebAuthn open source project +// +// Copyright (c) 2022 the Swift WebAuthn project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@testable import WebAuthn +import XCTest +import Crypto + +final class WebAuthnManagerChallengeTests: XCTestCase { + var webAuthnManager: WebAuthnManager! + + let relyingPartyID = "example.com" + let relyingPartyName = "Testy test" + let relyingPartyOrigin = "https://example.com" + + override func setUp() { + let configuration = WebAuthnManager.Configuration( + relyingPartyID: relyingPartyID, + relyingPartyName: relyingPartyName, + relyingPartyOrigin: relyingPartyOrigin + ) + webAuthnManager = .init(configuration: configuration) + } + + func testChallengeData() async throws { + let challengeGenerator = ChallengeGenerator.live + let challengeData : [UInt8] = [12,15,48,64] + + let challenge = challengeGenerator.generate(challengeData) + let extractedData = webAuthnManager.extractChallengeData(challenge: challenge) + + XCTAssertEqual(challengeData, extractedData) + } +} diff --git a/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift b/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift index 0102045..8cc6e9d 100644 --- a/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift +++ b/Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift @@ -25,7 +25,7 @@ final class WebAuthnManagerIntegrationTests: XCTestCase { ) let mockChallenge = [UInt8](repeating: 0, count: 5) - let challengeGenerator = ChallengeGenerator(generate: { mockChallenge }) + let challengeGenerator = ChallengeGenerator.mock(generate: mockChallenge ) let webAuthnManager = WebAuthnManager(configuration: configuration, challengeGenerator: challengeGenerator) // Step 1.: Begin Registration @@ -83,12 +83,12 @@ final class WebAuthnManagerIntegrationTests: XCTestCase { ) XCTAssertEqual(credential.id, mockCredentialID.base64EncodedString().asString()) - XCTAssertEqual(credential.attestationClientDataJSON.type, .create) + XCTAssertEqual(credential.attestationClientDataJSON.type, CollectedClientData.CeremonyType.create) XCTAssertEqual(credential.attestationClientDataJSON.origin, mockClientDataJSON.origin) XCTAssertEqual(credential.attestationClientDataJSON.challenge, mockChallenge.base64URLEncodedString()) XCTAssertEqual(credential.isBackedUp, false) XCTAssertEqual(credential.signCount, 0) - XCTAssertEqual(credential.type, .publicKey) + XCTAssertEqual(credential.type, CredentialType.publicKey) XCTAssertEqual(credential.publicKey, mockCredentialPublicKey) // Step 3.: Begin Authentication @@ -158,7 +158,7 @@ final class WebAuthnManagerIntegrationTests: XCTestCase { XCTAssertEqual(successfullAuthentication.newSignCount, 1) XCTAssertEqual(successfullAuthentication.credentialBackedUp, false) - XCTAssertEqual(successfullAuthentication.credentialDeviceType, .singleDevice) + XCTAssertEqual(successfullAuthentication.credentialDeviceType, VerifiedAuthentication.CredentialDeviceType.singleDevice) XCTAssertEqual(successfullAuthentication.credentialID, mockCredentialID.base64URLEncodedString()) // We did it!