Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Challenge generation more customisable #90

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
12 changes: 10 additions & 2 deletions Sources/WebAuthn/Helpers/ChallengeGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}
}
26 changes: 22 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 @@ -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,
Expand Down Expand Up @@ -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,
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