Skip to content

Commit 9fdfd9b

Browse files
authored
Integration tests (#21)
* add wip integration test * finalise integration test * fix comment date
1 parent d92d409 commit 9fdfd9b

File tree

1 file changed

+163
-0
lines changed

1 file changed

+163
-0
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the WebAuthn Swift open source project
4+
//
5+
// Copyright (c) 2023 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+
import Crypto
18+
19+
final class WebAuthnManagerIntegrationTests: XCTestCase {
20+
// swiftlint:disable:next function_body_length
21+
func testRegistrationAndAuthenticationSucceeds() async throws {
22+
let config = WebAuthnConfig(
23+
relyingPartyDisplayName: "Example RP",
24+
relyingPartyID: "example.com",
25+
relyingPartyOrigin: "https://example.com"
26+
)
27+
28+
let mockChallenge = [UInt8](repeating: 0, count: 5)
29+
let challengeGenerator = ChallengeGenerator(generate: { mockChallenge })
30+
let webAuthnManager = WebAuthnManager(config: config, challengeGenerator: challengeGenerator)
31+
32+
// Step 1.: Begin Registration
33+
let mockUser = MockUser()
34+
let timeout: TimeInterval = 1234
35+
let attestationPreference = AttestationConveyancePreference.none
36+
let publicKeyCredentialParameters = PublicKeyCredentialParameters.supported
37+
38+
let registrationOptions = try webAuthnManager.beginRegistration(
39+
user: mockUser,
40+
timeout: timeout,
41+
attestation: attestationPreference,
42+
publicKeyCredentialParameters: publicKeyCredentialParameters
43+
)
44+
45+
XCTAssertEqual(registrationOptions.challenge, mockChallenge.base64EncodedString())
46+
XCTAssertEqual(registrationOptions.user.id, mockUser.userID.toBase64().asString())
47+
XCTAssertEqual(registrationOptions.user.name, mockUser.name)
48+
XCTAssertEqual(registrationOptions.user.displayName, mockUser.displayName)
49+
XCTAssertEqual(registrationOptions.attestation, attestationPreference)
50+
XCTAssertEqual(registrationOptions.rp.id, config.relyingPartyID)
51+
XCTAssertEqual(registrationOptions.rp.name, config.relyingPartyDisplayName)
52+
XCTAssertEqual(registrationOptions.timeout, timeout)
53+
XCTAssertEqual(registrationOptions.pubKeyCredParams, publicKeyCredentialParameters)
54+
55+
// Now send `registrationOptions` to client, which in turn will send the authenticator's response back to us:
56+
// The following lines reflect what an authenticator normally produces
57+
let mockCredentialID = [UInt8](repeating: 1, count: 10).base64URLEncodedString()
58+
let mockClientDataJSON = TestClientDataJSON(challenge: mockChallenge.base64URLEncodedString())
59+
let mockCredentialPublicKey = TestCredentialPublicKeyBuilder().validMock().buildAsByteArray()
60+
let mockAttestationObject = TestAttestationObjectBuilder().validMock().authData(
61+
TestAuthDataBuilder().validMock()
62+
.attestedCredData(credentialPublicKey: mockCredentialPublicKey)
63+
.noExtensionData()
64+
)
65+
66+
let registrationResponse = RegistrationCredential(
67+
id: mockCredentialID.asString(),
68+
type: "public-key",
69+
rawID: mockCredentialID,
70+
attestationResponse: AuthenticatorAttestationResponse(
71+
clientDataJSON: mockClientDataJSON.base64URLEncoded,
72+
attestationObject: mockAttestationObject.buildBase64URLEncoded()
73+
)
74+
)
75+
76+
// Step 2.: Finish Registration
77+
let credential = try await webAuthnManager.finishRegistration(
78+
challenge: mockChallenge.base64EncodedString(),
79+
credentialCreationData: registrationResponse,
80+
requireUserVerification: true,
81+
supportedPublicKeyAlgorithms: publicKeyCredentialParameters,
82+
pemRootCertificatesByFormat: [:],
83+
confirmCredentialIDNotRegisteredYet: { _ in true }
84+
)
85+
86+
XCTAssertEqual(credential.id, mockCredentialID.asString())
87+
XCTAssertEqual(credential.attestationClientDataJSON.type, .create)
88+
XCTAssertEqual(credential.attestationClientDataJSON.origin, mockClientDataJSON.origin)
89+
XCTAssertEqual(credential.attestationClientDataJSON.challenge, mockChallenge.base64URLEncodedString())
90+
XCTAssertEqual(credential.isBackedUp, false)
91+
XCTAssertEqual(credential.signCount, 0)
92+
XCTAssertEqual(credential.type, "public-key")
93+
XCTAssertEqual(credential.publicKey, mockCredentialPublicKey)
94+
95+
// Step 3.: Begin Authentication
96+
let authenticationTimeout: TimeInterval = 4567
97+
let userVerification: UserVerificationRequirement = .preferred
98+
let rememberedCredentials = [PublicKeyCredentialDescriptor(
99+
type: "public-key",
100+
id: [UInt8](URLEncodedBase64(credential.id).urlDecoded.decoded!)
101+
)]
102+
103+
let authenticationOptions = try webAuthnManager.beginAuthentication(
104+
challenge: mockChallenge.base64EncodedString(),
105+
timeout: authenticationTimeout,
106+
allowCredentials: rememberedCredentials,
107+
userVerification: userVerification
108+
)
109+
110+
XCTAssertEqual(authenticationOptions.rpId, config.relyingPartyID)
111+
XCTAssertEqual(authenticationOptions.timeout, authenticationTimeout)
112+
XCTAssertEqual(authenticationOptions.challenge, mockChallenge.base64EncodedString())
113+
XCTAssertEqual(authenticationOptions.userVerification, userVerification)
114+
XCTAssertEqual(authenticationOptions.allowCredentials, rememberedCredentials)
115+
116+
// Now send `authenticationOptions` to client, which in turn will send the authenticator's response back to us:
117+
// The following lines reflect what an authenticator normally produces
118+
let authenticatorData = TestAuthDataBuilder().validAuthenticationMock()
119+
.rpIDHash(fromRpID: config.relyingPartyID)
120+
.counter([0, 0, 0, 1]) // we authenticated once now, so authenticator likely increments the sign counter
121+
.buildAsBase64URLEncoded()
122+
123+
// Authenticator creates a signature with private key
124+
let clientData: Data = TestClientDataJSON(
125+
type: "webauthn.get",
126+
challenge: mockChallenge.base64URLEncodedString()
127+
).jsonData
128+
let clientDataHash = SHA256.hash(data: clientData)
129+
let rawAuthenticatorData = authenticatorData.urlDecoded.decoded!
130+
let signatureBase = rawAuthenticatorData + clientDataHash
131+
let signature = try TestECCKeyPair.signature(data: signatureBase).derRepresentation
132+
133+
let authenticationCredential = AuthenticationCredential(
134+
id: mockCredentialID,
135+
response: AuthenticatorAssertionResponse(
136+
clientDataJSON: clientData.base64URLEncodedString(),
137+
authenticatorData: authenticatorData,
138+
signature: signature.base64URLEncodedString(),
139+
userHandle: mockUser.userID,
140+
attestationObject: nil
141+
),
142+
authenticatorAttachment: "platform",
143+
type: "public-key"
144+
)
145+
146+
// Step 4.: Finish Authentication
147+
let oldSignCount: UInt32 = 0
148+
let successfullAuthentication = try webAuthnManager.finishAuthentication(
149+
credential: authenticationCredential,
150+
expectedChallenge: mockChallenge.base64URLEncodedString(),
151+
credentialPublicKey: mockCredentialPublicKey,
152+
credentialCurrentSignCount: oldSignCount,
153+
requireUserVerification: false
154+
)
155+
156+
XCTAssertEqual(successfullAuthentication.newSignCount, 1)
157+
XCTAssertEqual(successfullAuthentication.credentialBackedUp, false)
158+
XCTAssertEqual(successfullAuthentication.credentialDeviceType, .singleDevice)
159+
XCTAssertEqual(successfullAuthentication.credentialID, mockCredentialID)
160+
161+
// We did it!
162+
}
163+
}

0 commit comments

Comments
 (0)