Skip to content

Commit

Permalink
Allow custom data addon on challenge while keeping random generation …
Browse files Browse the repository at this point in the history
…internal
  • Loading branch information
dscreve committed Jan 29, 2025
1 parent 1d84f44 commit 352487d
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 20 deletions.
20 changes: 9 additions & 11 deletions Sources/WebAuthn/Helpers/ChallengeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,16 @@

import Foundation

public struct ChallengeGenerator: Sendable {
var generate: @Sendable () -> [UInt8]
package struct ChallengeGenerator: Sendable {
static let challengeSize: Int = 32

var generate: @Sendable (_ : [UInt8]) -> [UInt8]

public static var live: Self {
.init(generate: {
// try to use secured random generator
var bytes = [UInt8](repeating: 0, count: 32)
let result = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
guard result == errSecSuccess else {
return [UInt8].random(count: 32)
}
return bytes
package static var live: Self {
.init(generate: { challengeData in
var randomData = [UInt8].random(count: challengeSize)
randomData.append(contentsOf: challengeData)
return randomData
})
}
}
24 changes: 20 additions & 4 deletions Sources/WebAuthn/WebAuthnManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -60,9 +76,9 @@ public struct WebAuthnManager: Sendable {
timeout: Duration? = .seconds(5*60),
attestation: AttestationConveyancePreference = .none,
publicKeyCredentialParameters: [PublicKeyCredentialParameters] = .supported,
challenge : [UInt8]? = nil
challengeData : [UInt8] = []
) -> PublicKeyCredentialCreationOptions {
let challenge = challenge ?? challengeGenerator.generate()
let challenge = challengeGenerator.generate(challengeData)

return PublicKeyCredentialCreationOptions(
challenge: challenge,
Expand Down Expand Up @@ -140,9 +156,9 @@ public struct WebAuthnManager: Sendable {
timeout: Duration? = .seconds(60),
allowCredentials: [PublicKeyCredentialDescriptor]? = nil,
userVerification: UserVerificationRequirement = .preferred,
challenge : [UInt8]? = nil
challengeData : [UInt8] = []
) throws -> PublicKeyCredentialRequestOptions {
let challenge = challenge ?? challengeGenerator.generate()
let challenge = challengeGenerator.generate(challengeData)

return PublicKeyCredentialRequestOptions(
challenge: challenge,
Expand Down
2 changes: 1 addition & 1 deletion Tests/WebAuthnTests/Mocks/MockChallengeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@

extension ChallengeGenerator {
static func mock(generate: [UInt8]) -> Self {
ChallengeGenerator(generate: { generate })
ChallengeGenerator(generate: { _ in generate })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,5 @@ final class WebAuthnManagerAuthenticationTests: XCTestCase {
requireUserVerification: requireUserVerification
)
}

}
43 changes: 43 additions & 0 deletions Tests/WebAuthnTests/WebAuthnManagerChallengeTests.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
8 changes: 4 additions & 4 deletions Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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!
Expand Down

0 comments on commit 352487d

Please sign in to comment.