Skip to content

Commit 7b94b64

Browse files
committed
add tests
1 parent 2987d0d commit 7b94b64

11 files changed

+141
-110
lines changed

Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public struct PublicKeyCredentialCreationOptions: Codable {
2525

2626
// MARK: - Credential parameters
2727

28-
public struct PublicKeyCredentialParameters: Codable {
28+
public struct PublicKeyCredentialParameters: Equatable, Codable {
2929
public let type: String
3030
public let algorithm: COSEAlgorithmIdentifier
3131

Sources/WebAuthn/Helpers/Base64Utilities.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ extension String {
5353
.replacingOccurrences(of: "/", with: "_")
5454
.replacingOccurrences(of: "=", with: "")
5555
}
56+
57+
func toBase64() -> String {
58+
return Data(self.utf8).base64EncodedString()
59+
}
5660
}
5761

5862
extension String {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Foundation
2+
3+
public struct ChallengeGenerator {
4+
var generate: () -> [UInt8]
5+
6+
public static var live: Self {
7+
.init(generate: { [UInt8].random(count: 32) })
8+
}
9+
}

Sources/WebAuthn/WebAuthnManager.swift

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,32 @@ import SwiftCBOR
2020
public struct WebAuthnManager {
2121
private let config: WebAuthnConfig
2222

23-
public init(config: WebAuthnConfig) {
23+
private let challengeGenerator: ChallengeGenerator
24+
25+
public init(config: WebAuthnConfig, challengeGenerator: ChallengeGenerator = .live) {
2426
self.config = config
27+
self.challengeGenerator = challengeGenerator
2528
}
2629

2730
/// Generate a new set of registration data to be sent to the client and authenticator.
28-
public func beginRegistration(user: User) throws -> PublicKeyCredentialCreationOptions {
31+
public func beginRegistration(
32+
user: User,
33+
publicKeyCredentialParameters: [PublicKeyCredentialParameters] = PublicKeyCredentialParameters.supported
34+
) throws -> PublicKeyCredentialCreationOptions {
2935
guard let base64ID = user.userID.data(using: .utf8)?.base64EncodedString() else {
3036
throw WebAuthnError.invalidUserID
3137
}
3238

3339
let userEntity = PublicKeyCredentialUserEntity(name: user.name, id: base64ID, displayName: user.displayName)
3440
let relyingParty = PublicKeyCredentialRpEntity(name: config.relyingPartyDisplayName, id: config.relyingPartyID)
3541

36-
let challenge = try generateChallengeString()
42+
let challenge = challengeGenerator.generate()
3743

3844
return PublicKeyCredentialCreationOptions(
3945
challenge: challenge.base64EncodedString(),
4046
user: userEntity,
4147
relyingParty: relyingParty,
42-
publicKeyCredentialParameters: PublicKeyCredentialParameters.supported,
48+
publicKeyCredentialParameters: publicKeyCredentialParameters,
4349
timeout: config.timeout
4450
)
4551
}
@@ -105,7 +111,7 @@ public struct WebAuthnManager {
105111
attestation: String? = nil,
106112
attestationFormats: [String]? = nil
107113
) throws -> PublicKeyCredentialRequestOptions {
108-
let challenge = try challenge ?? generateChallengeString().base64EncodedString()
114+
let challenge = challenge ?? challengeGenerator.generate().base64EncodedString()
109115
return PublicKeyCredentialRequestOptions(
110116
challenge: challenge,
111117
timeout: timeout,
@@ -180,12 +186,3 @@ public struct WebAuthnManager {
180186
)
181187
}
182188
}
183-
184-
extension WebAuthnManager {
185-
/// Generate a suitably random value to be used as an attestation or assertion challenge
186-
/// - Throws: An error if something went wrong while generating random byte
187-
/// - Returns: 32 bytes
188-
public func generateChallengeString() throws -> [UInt8] {
189-
[UInt8].random(count: 32)
190-
}
191-
}

Tests/WebAuthnTests/CredentialCreationResponseTests.swift

Lines changed: 0 additions & 26 deletions
This file was deleted.

Tests/WebAuthnTests/HelpersTests.swift

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,6 @@ import XCTest
33
@testable import WebAuthn
44

55
final class HelpersTests: XCTestCase {
6-
func testGenerateChallengeReturnsRandomBytes() throws {
7-
let webAuthn = WebAuthnManager(
8-
config: .init(
9-
relyingPartyDisplayName: "123",
10-
relyingPartyID: "1",
11-
relyingPartyOrigin: "http://localhost:8080",
12-
timeout: 60
13-
)
14-
)
15-
let challenge1 = try webAuthn.generateChallengeString()
16-
let challenge2 = try webAuthn.generateChallengeString()
17-
18-
XCTAssertNotEqual(challenge1, challenge2)
19-
}
20-
216
func testBase64URLEncodeReturnsCorrectString() {
227
let input: [UInt8] = [1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0]
238
let expectedBase64 = "AQABAAEBAAEAAQEAAAABAA=="
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@testable import WebAuthn
2+
3+
extension ChallengeGenerator {
4+
static func mock(generate: [UInt8]) -> Self {
5+
ChallengeGenerator(generate: { generate })
6+
}
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import WebAuthn
2+
3+
struct MockUser: User {
4+
var userID: String
5+
var name: String
6+
var displayName: String
7+
8+
init(userID: String = "1", name: String = "John", displayName: String = "Johnny") {
9+
self.userID = userID
10+
self.name = name
11+
self.displayName = displayName
12+
}
13+
}

Tests/WebAuthnTests/ParsedCredentialCreationResponseTests.swift

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the WebAuthn Swift open source project
4+
//
5+
// Copyright (c) 2022 the WebAuthn Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
@testable import WebAuthn
16+
import XCTest
17+
18+
final class WebAuthnManagerTests: XCTestCase {
19+
var webAuthnManager: WebAuthnManager!
20+
21+
let challenge: [UInt8] = [1, 0, 1]
22+
let relyingPartyDisplayName = "Testy test"
23+
let relyingPartyID = "example.com"
24+
let relyingPartyOrigin = "https://example.com"
25+
let timeout: TimeInterval = 6000
26+
27+
override func setUp() {
28+
let config = WebAuthnConfig(
29+
relyingPartyDisplayName: relyingPartyDisplayName,
30+
relyingPartyID: relyingPartyID,
31+
relyingPartyOrigin: relyingPartyOrigin,
32+
timeout: timeout
33+
)
34+
webAuthnManager = .init(config: config, challengeGenerator: .mock(generate: challenge))
35+
}
36+
37+
func testBeginRegistrationReturns() throws {
38+
let user = MockUser()
39+
let publicKeyCredentialParameter = PublicKeyCredentialParameters(type: "public-key", algorithm: .algPS384)
40+
let options = try webAuthnManager.beginRegistration(
41+
user: user,
42+
publicKeyCredentialParameters: [publicKeyCredentialParameter]
43+
)
44+
45+
XCTAssertEqual(options.challenge, challenge.base64EncodedString())
46+
XCTAssertEqual(options.relyingParty.id, relyingPartyID)
47+
XCTAssertEqual(options.relyingParty.name, relyingPartyDisplayName)
48+
XCTAssertEqual(options.timeout, timeout)
49+
XCTAssertEqual(options.user.id, user.userID.toBase64())
50+
XCTAssertEqual(options.user.displayName, user.displayName)
51+
XCTAssertEqual(options.user.name, user.name)
52+
XCTAssertEqual(options.publicKeyCredentialParameters, [publicKeyCredentialParameter])
53+
}
54+
55+
func testFinishRegistrationFailsWithInvalidRawID() async throws {
56+
do {
57+
_ = try await finishRegistration(rawID: "_")
58+
XCTFail("Should not succeed")
59+
} catch {
60+
XCTAssertEqual(error as! WebAuthnError, .invalidRawID)
61+
}
62+
}
63+
64+
func testFinishRegistrationFailsWithInvalidCredentialCreationType() async throws {
65+
do {
66+
_ = try await finishRegistration(type: "foo")
67+
XCTFail("Should not succeed")
68+
} catch {
69+
XCTAssertEqual(error as! WebAuthnError, .invalidCredentialCreationType)
70+
}
71+
}
72+
73+
private func finishRegistration(
74+
challenge: EncodedBase64 = "xxi54jsOKKj7GrikECpuQyenfMC31FADtT6/fE9+fMY=",
75+
id: EncodedBase64 = "4PrJNQUJ9xdI2DeCzK9rTBRixhXHDiVdoTROQIh8j80",
76+
type: String = "public-key",
77+
rawID: EncodedBase64 = "4PrJNQUJ9xdI2DeCzK9rTBRixhXHDiVdoTROQIh8j80",
78+
clientDataJSON: String = "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiY21GdVpHOXRVM1J5YVc1blJuSnZiVk5sY25abGNnIiwib3JpZ2luIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiY3Jvc3NPcmlnaW4iOmZhbHNlLCJvdGhlcl9rZXlzX2Nhbl9iZV9hZGRlZF9oZXJlIjoiZG8gbm90IGNvbXBhcmUgY2xpZW50RGF0YUpTT04gYWdhaW5zdCBhIHRlbXBsYXRlLiBTZWUgaHR0cHM6Ly9nb28uZ2wveWFiUGV4In0",
79+
attestationObject: String = "o2NmbXRmcGFja2VkZ2F0dFN0bXSiY2FsZyZjc2lnWEcwRQIgNTRtpI_SOOZVzU1pN_4cX-osqUPiHMOW48qqq91DXfUCIQC-MHiaIxt2OdIxgqYnyUDHceevNOMfPibenabQGvXgjGhhdXRoRGF0YVikSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAK3OAAI1vMYKZIsLJfHwVQMAIDo-5W3Kur7A7y9Lfw7ijhExfCz3_5coMEQNY_y6p-JrpQECAyYgASFYIJr_yLoYbYWgcf7aQcd7pcjUj-3o8biafWQH28WijQSvIlggPI2KqqRQ26KKuFaJ0yH7nouCBrzHu8qRONW-CPa9VDM",
80+
confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool = { _ in true }
81+
) async throws -> Credential {
82+
try await webAuthnManager.finishRegistration(
83+
challenge: challenge,
84+
credentialCreationData: RegistrationCredential(
85+
id: id,
86+
type: type,
87+
rawID: rawID,
88+
attestationResponse: AuthenticatorAttestationResponse(
89+
clientDataJSON: clientDataJSON,
90+
attestationObject: attestationObject
91+
)
92+
),
93+
confirmCredentialIDNotRegisteredYet: confirmCredentialIDNotRegisteredYet
94+
)
95+
}
96+
}

0 commit comments

Comments
 (0)