Skip to content

Commit 5d0efcb

Browse files
authored
Merge branch 'main' into dimitry/relying-party-spelling
2 parents f418f1f + 956ea25 commit 5d0efcb

File tree

5 files changed

+126
-9
lines changed

5 files changed

+126
-9
lines changed

Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,24 +78,31 @@ extension AuthenticatorData {
7878

7979
}
8080

81-
/// Returns: Attested credentials data and the length
81+
/// Parse and return the attested credential data and its length.
82+
///
83+
/// This is assumed to take place after the first 37 bytes of `data`, which is always of fixed size.
84+
/// - SeeAlso: [WebAuthn Level 3 Editor's Draft §6.5.1. Attested Credential Data]( https://w3c.github.io/webauthn/#sctn-attested-credential-data)
8285
private static func parseAttestedData(_ data: Data) throws -> (AttestedCredentialData, Int) {
83-
// We've parsed the first 37 bytes so far, the next bytes now should be the attested credential data
84-
// See https://w3c.github.io/webauthn/#sctn-attested-credential-data
86+
/// **aaguid** (16): The AAGUID of the authenticator.
8587
let aaguidLength = 16
8688
let aaguid = data[37..<(37 + aaguidLength)] // To byte at index 52
8789

90+
/// **credentialIdLength** (2): Byte length L of credentialId, 16-bit unsigned big-endian integer. Value MUST be ≤ 1023.
8891
let idLengthBytes = data[53..<55] // Length is 2 bytes
8992
let idLengthData = Data(idLengthBytes)
9093
let idLength: UInt16 = idLengthData.toInteger(endian: .big)
94+
95+
guard idLength <= 1023
96+
else { throw WebAuthnError.credentialIDTooLong }
97+
9198
let credentialIDEndIndex = Int(idLength) + 55
99+
guard data.count >= credentialIDEndIndex
100+
else { throw WebAuthnError.credentialIDTooShort }
92101

93-
guard data.count >= credentialIDEndIndex else {
94-
throw WebAuthnError.credentialIDTooShort
95-
}
102+
/// **credentialId** (L): Credential ID
96103
let credentialID = data[55..<credentialIDEndIndex]
97104

98-
/// **credentialPublicKey** (variable): The credential public key encoded in COSE_Key format, as defined in Section 7 of [RFC9052], using the CTAP2 canonical CBOR encoding form.
105+
/// **credentialPublicKey** (variable): The credential public key encoded in `COSE_Key` format, as defined in [Section 7](https://tools.ietf.org/html/rfc9052#section-7) of [RFC9052], using the CTAP2 canonical CBOR encoding form.
99106
/// Assuming valid CBOR, verify the public key's length by decoding the next CBOR item.
100107
let inputStream = ByteInputStream(data[credentialIDEndIndex...])
101108
let decoder = CBORDecoder(stream: inputStream)
@@ -108,7 +115,7 @@ extension AuthenticatorData {
108115
publicKey: Array(publicKeyBytes)
109116
)
110117

111-
// 2 is the bytes storing the size of the credential ID
118+
/// `2` is the size of **credentialIdLength**
112119
let length = data.aaguid.count + 2 + data.credentialID.count + data.publicKey.count
113120

114121
return (data, length)

Sources/WebAuthn/WebAuthnError.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public enum WebAuthnError: Error, Equatable {
4949
case attestedCredentialFlagNotSet
5050
case extensionDataMissing
5151
case leftOverBytesInAuthenticatorData
52+
case credentialIDTooLong
5253
case credentialIDTooShort
5354

5455
// MARK: CredentialPublicKey

Tests/WebAuthnTests/WebAuthnManagerRegistrationTests.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,47 @@ final class WebAuthnManagerRegistrationTests: XCTestCase {
291291
expect: WebAuthnError.credentialRawIDTooLong
292292
)
293293
}
294+
295+
func testFinishAuthenticationFailsIfCredentialIDTooLong() async throws {
296+
/// This should succeed as it's on the border of being acceptable
297+
_ = try await finishRegistration(
298+
attestationObject: TestAttestationObjectBuilder()
299+
.validMock()
300+
.authData(
301+
TestAuthDataBuilder()
302+
.validMock()
303+
.attestedCredData(
304+
aaguid: Array(repeating: 0, count: 16),
305+
credentialIDLength: [0b000_00011, 0b1111_1111],
306+
credentialID: Array(repeating: 0, count: 1023),
307+
credentialPublicKey: TestCredentialPublicKeyBuilder().validMock().buildAsByteArray()
308+
)
309+
)
310+
.build()
311+
.cborEncoded
312+
)
313+
314+
/// While this one should throw
315+
try await assertThrowsError(
316+
await finishRegistration(
317+
attestationObject: TestAttestationObjectBuilder()
318+
.validMock()
319+
.authData(
320+
TestAuthDataBuilder()
321+
.validMock()
322+
.attestedCredData(
323+
aaguid: Array(repeating: 0, count: 16),
324+
credentialIDLength: [0b000_00100, 0b0000_0000],
325+
credentialID: Array(repeating: 0, count: 1024),
326+
credentialPublicKey: TestCredentialPublicKeyBuilder().validMock().buildAsByteArray()
327+
)
328+
)
329+
.build()
330+
.cborEncoded
331+
),
332+
expect: WebAuthnError.credentialIDTooLong
333+
)
334+
}
294335

295336
func testFinishRegistrationSucceeds() async throws {
296337
let credentialID: [UInt8] = [0, 1, 0, 1, 0, 1]

docker/docker-compose.2204.main.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ services:
66
image: webauthn-swift:22.04-main
77
build:
88
args:
9-
base_image: "swiftlang/swift:nightly-main-focal"
9+
base_image: "swiftlang/swift:nightly-main-jammy"
1010

1111
test:
1212
image: webauthn-swift:22.04-main

scripts/check_no_api_breakages.sh

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/bin/bash
2+
##===----------------------------------------------------------------------===##
3+
##
4+
## This source file is part of the WebAuthn Swift open source project
5+
##
6+
## Copyright (c) 2024 the WebAuthn Swift project authors
7+
## Licensed under Apache License v2.0
8+
##
9+
## See LICENSE.txt for license information
10+
## See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
11+
##
12+
## SPDX-License-Identifier: Apache-2.0
13+
##
14+
##===----------------------------------------------------------------------===##
15+
16+
##===----------------------------------------------------------------------===##
17+
##
18+
## This source file is part of the SwiftNIO open source project
19+
##
20+
## Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
21+
## Licensed under Apache License v2.0
22+
##
23+
## See LICENSE.txt for license information
24+
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
25+
##
26+
## SPDX-License-Identifier: Apache-2.0
27+
##
28+
##===----------------------------------------------------------------------===##
29+
30+
set -eu
31+
32+
function usage() {
33+
echo >&2 "Usage: $0 REPO-GITHUB-URL NEW-VERSION OLD-VERSIONS..."
34+
echo >&2
35+
echo >&2 "This script requires a Swift 5.6+ toolchain."
36+
echo >&2
37+
echo >&2 "Examples:"
38+
echo >&2
39+
echo >&2 "Check between main and tag 1.0.0 of webauthn-swift:"
40+
echo >&2 " $0 https://github.com/swift-server/webauthn-swift main 1.0.0"
41+
echo >&2
42+
echo >&2 "Check between HEAD and commit 681eb6f using the provided toolchain:"
43+
echo >&2 " xcrun --toolchain org.swift.5120190702a $0 ../some-local-repo HEAD 681eb6f"
44+
}
45+
46+
if [[ $# -lt 3 ]]; then
47+
usage
48+
exit 1
49+
fi
50+
51+
tmpdir=$(mktemp -d /tmp/.check-api_XXXXXX)
52+
repo_url=$1
53+
new_tag=$2
54+
shift 2
55+
56+
repodir="$tmpdir/repo"
57+
git clone "$repo_url" "$repodir"
58+
git -C "$repodir" fetch -q origin '+refs/pull/*:refs/remotes/origin/pr/*'
59+
cd "$repodir"
60+
git checkout -q "$new_tag"
61+
62+
for old_tag in "$@"; do
63+
echo "Checking public API breakages from $old_tag to $new_tag"
64+
65+
swift package diagnose-api-breaking-changes "$old_tag"
66+
done
67+
68+
echo done

0 commit comments

Comments
 (0)