Skip to content

Commit 8cbe548

Browse files
Updated throws to specify types when possible
1 parent 2e1458c commit 8cbe548

File tree

7 files changed

+80
-75
lines changed

7 files changed

+80
-75
lines changed

Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@ struct AuthenticatorData: Equatable, Sendable {
2727
}
2828

2929
extension AuthenticatorData {
30-
init(bytes: [UInt8]) throws {
30+
init(bytes: [UInt8]) throws(WebAuthnError) {
3131
let minAuthDataLength = 37
32-
guard bytes.count >= minAuthDataLength else {
33-
throw WebAuthnError.authDataTooShort
34-
}
32+
guard bytes.count >= minAuthDataLength
33+
else { throw .authDataTooShort }
3534

3635
let relyingPartyIDHash = Array(bytes[..<32])
3736
let flags = AuthenticatorFlags(bytes[32])
@@ -43,69 +42,69 @@ extension AuthenticatorData {
4342
// For attestation signatures, the authenticator MUST set the AT flag and include the attestedCredentialData.
4443
if flags.attestedCredentialData {
4544
let minAttestedAuthLength = 37 + AAGUID.size + 2
46-
guard bytes.count > minAttestedAuthLength else {
47-
throw WebAuthnError.attestedCredentialDataMissing
48-
}
45+
guard bytes.count > minAttestedAuthLength
46+
else { throw .attestedCredentialDataMissing }
47+
4948
let (data, length) = try Self.parseAttestedData(bytes)
5049
attestedCredentialData = data
5150
remainingCount -= length
5251
// For assertion signatures, the AT flag MUST NOT be set and the attestedCredentialData MUST NOT be included.
5352
} else {
5453
if !flags.extensionDataIncluded && bytes.count != minAuthDataLength {
55-
throw WebAuthnError.attestedCredentialFlagNotSet
54+
throw .attestedCredentialFlagNotSet
5655
}
5756
}
5857

5958
var extensionData: [UInt8]?
6059
if flags.extensionDataIncluded {
61-
guard remainingCount != 0 else {
62-
throw WebAuthnError.extensionDataMissing
63-
}
60+
guard remainingCount != 0
61+
else { throw .extensionDataMissing }
62+
6463
extensionData = Array(bytes[(bytes.count - remainingCount)...])
6564
remainingCount -= extensionData!.count
6665
}
6766

68-
guard remainingCount == 0 else {
69-
throw WebAuthnError.leftOverBytesInAuthenticatorData
70-
}
67+
guard remainingCount == 0
68+
else { throw .leftOverBytesInAuthenticatorData }
7169

7270
self.relyingPartyIDHash = relyingPartyIDHash
7371
self.flags = flags
7472
self.counter = counter
7573
self.attestedData = attestedCredentialData
7674
self.extData = extensionData
77-
7875
}
7976

8077
/// Parse and return the attested credential data and its length.
8178
///
8279
/// This is assumed to take place after the first 37 bytes of `data`, which is always of fixed size.
8380
/// - SeeAlso: [WebAuthn Level 3 Editor's Draft §6.5.1. Attested Credential Data]( https://w3c.github.io/webauthn/#sctn-attested-credential-data)
84-
private static func parseAttestedData(_ data: [UInt8]) throws -> (AttestedCredentialData, Int) {
81+
private static func parseAttestedData(_ data: [UInt8]) throws(WebAuthnError) -> (AttestedCredentialData, Int) {
8582
/// **aaguid** (16): The AAGUID of the authenticator.
8683
guard let aaguid = AAGUID(bytes: data[37..<(37 + AAGUID.size)]) // Bytes [37-52]
87-
else { throw WebAuthnError.attestedCredentialDataMissing }
84+
else { throw .attestedCredentialDataMissing }
8885

8986
/// **credentialIdLength** (2): Byte length L of credentialId, 16-bit unsigned big-endian integer. Value MUST be ≤ 1023.
9087
let idLengthBytes = data[53..<55] // Length is 2 bytes
9188
let idLengthData = Data(idLengthBytes)
9289
let idLength = UInt16(bigEndianBytes: idLengthData)
9390

9491
guard idLength <= 1023
95-
else { throw WebAuthnError.credentialIDTooLong }
92+
else { throw .credentialIDTooLong }
9693

9794
let credentialIDEndIndex = Int(idLength) + 55
9895
guard data.count >= credentialIDEndIndex
99-
else { throw WebAuthnError.credentialIDTooShort }
96+
else { throw .credentialIDTooShort }
10097

10198
/// **credentialId** (L): Credential ID
10299
let credentialID = data[55..<credentialIDEndIndex]
103100

104101
/// **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.
105-
/// Assuming valid CBOR, verify the public key's length by decoding the next CBOR item.
102+
/// Assuming valid CBOR, verify the public key's length by decoding the next CBOR item, and checking how much data is left on the stream.
106103
let inputStream = ByteInputStream(data[credentialIDEndIndex...])
107-
let decoder = CBORDecoder(stream: inputStream, options: CBOROptions(maximumDepth: 16))
108-
_ = try decoder.decodeItem()
104+
do {
105+
let decoder = CBORDecoder(stream: inputStream, options: CBOROptions(maximumDepth: 16))
106+
_ = try decoder.decodeItem()
107+
} catch { throw .invalidPublicKeyLength }
109108
let publicKeyBytes = data[credentialIDEndIndex..<(data.count - inputStream.remainingBytes)]
110109

111110
let data = AttestedCredentialData(
@@ -132,13 +131,13 @@ class ByteInputStream: CBORInputStream {
132131
/// The remaining bytes in the original data buffer.
133132
var remainingBytes: Int { slice.count }
134133

135-
func popByte() throws -> UInt8 {
136-
if slice.count < 1 { throw CBORError.unfinishedSequence }
134+
func popByte() throws(CBORError) -> UInt8 {
135+
if slice.count < 1 { throw .unfinishedSequence }
137136
return slice.removeFirst()
138137
}
139138

140-
func popBytes(_ n: Int) throws -> ArraySlice<UInt8> {
141-
if slice.count < n { throw CBORError.unfinishedSequence }
139+
func popBytes(_ n: Int) throws(CBORError) -> ArraySlice<UInt8> {
140+
if slice.count < n { throw .unfinishedSequence }
142141
let result = slice.prefix(n)
143142
slice = slice.dropFirst(n)
144143
return result

Sources/WebAuthn/Ceremonies/Shared/CollectedClientData.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,14 @@ public struct CollectedClientData: Codable, Hashable, Sendable {
3434
public let challenge: URLEncodedBase64
3535
public let origin: String
3636

37-
func verify(storedChallenge: [UInt8], ceremonyType: CeremonyType, relyingPartyOrigin: String) throws {
38-
guard type == ceremonyType else { throw CollectedClientDataVerifyError.ceremonyTypeDoesNotMatch }
39-
guard challenge == storedChallenge.base64URLEncodedString() else {
40-
throw CollectedClientDataVerifyError.challengeDoesNotMatch
41-
}
42-
guard origin == relyingPartyOrigin else { throw CollectedClientDataVerifyError.originDoesNotMatch }
37+
func verify(storedChallenge: [UInt8], ceremonyType: CeremonyType, relyingPartyOrigin: String) throws(CollectedClientDataVerifyError) {
38+
guard type == ceremonyType
39+
else { throw .ceremonyTypeDoesNotMatch }
40+
41+
guard challenge == storedChallenge.base64URLEncodedString()
42+
else { throw .challengeDoesNotMatch }
43+
44+
guard origin == relyingPartyOrigin
45+
else { throw .originDoesNotMatch }
4346
}
4447
}

Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -105,28 +105,28 @@ struct EC2PublicKey: PublicKey, Sendable {
105105
self.yCoordinate = yCoordinate
106106
}
107107

108-
init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws {
108+
init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws(WebAuthnError) {
109109
self.algorithm = algorithm
110110

111-
// Curve is key -1 - or -0 for SwiftCBOR
112-
// X Coordinate is key -2, or NegativeInt 1 for SwiftCBOR
113-
// Y Coordinate is key -3, or NegativeInt 2 for SwiftCBOR
114-
guard let curveRaw = publicKeyObject[COSEKey.crv.cbor],
111+
/// Curve is key -1 - or -0 for SwiftCBOR
112+
/// X Coordinate is key -2, or NegativeInt 1 for SwiftCBOR
113+
/// Y Coordinate is key -3, or NegativeInt 2 for SwiftCBOR
114+
guard
115+
let curveRaw = publicKeyObject[COSEKey.crv.cbor],
115116
case let .unsignedInt(curve) = curveRaw,
116-
let coseCurve = COSECurve(rawValue: curve) else {
117-
throw WebAuthnError.invalidCurve
118-
}
117+
let coseCurve = COSECurve(rawValue: curve)
118+
else { throw .invalidCurve }
119119
self.curve = coseCurve
120120

121-
guard let xCoordRaw = publicKeyObject[COSEKey.x.cbor],
122-
case let .byteString(xCoordinateBytes) = xCoordRaw else {
123-
throw WebAuthnError.invalidXCoordinate
124-
}
121+
guard
122+
let xCoordRaw = publicKeyObject[COSEKey.x.cbor],
123+
case let .byteString(xCoordinateBytes) = xCoordRaw
124+
else { throw .invalidXCoordinate }
125125
xCoordinate = xCoordinateBytes
126-
guard let yCoordRaw = publicKeyObject[COSEKey.y.cbor],
127-
case let .byteString(yCoordinateBytes) = yCoordRaw else {
128-
throw WebAuthnError.invalidYCoordinate
129-
}
126+
guard
127+
let yCoordRaw = publicKeyObject[COSEKey.y.cbor],
128+
case let .byteString(yCoordinateBytes) = yCoordRaw
129+
else { throw .invalidYCoordinate }
130130
yCoordinate = yCoordinateBytes
131131
}
132132

@@ -164,23 +164,23 @@ struct RSAPublicKeyData: PublicKey, Sendable {
164164

165165
var rawRepresentation: [UInt8] { n + e }
166166

167-
init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws {
167+
init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws(WebAuthnError) {
168168
self.algorithm = algorithm
169169

170-
guard let nRaw = publicKeyObject[COSEKey.n.cbor],
171-
case let .byteString(nBytes) = nRaw else {
172-
throw WebAuthnError.invalidModulus
173-
}
170+
guard
171+
let nRaw = publicKeyObject[COSEKey.n.cbor],
172+
case let .byteString(nBytes) = nRaw
173+
else { throw .invalidModulus }
174174
n = nBytes
175175

176-
guard let eRaw = publicKeyObject[COSEKey.e.cbor],
177-
case let .byteString(eBytes) = eRaw else {
178-
throw WebAuthnError.invalidExponent
179-
}
176+
guard
177+
let eRaw = publicKeyObject[COSEKey.e.cbor],
178+
case let .byteString(eBytes) = eRaw
179+
else { throw .invalidExponent }
180180
e = eBytes
181181
}
182182

183-
func verify(signature: some DataProtocol, data: some DataProtocol) throws {
183+
func verify(signature: some DataProtocol, data: some DataProtocol) throws(WebAuthnError) {
184184
throw WebAuthnError.unsupported
185185
// let rsaSignature = _RSA.Signing.RSASignature(derRepresentation: signature)
186186

@@ -210,22 +210,24 @@ struct OKPPublicKey: PublicKey, Sendable {
210210
let curve: UInt64
211211
let xCoordinate: [UInt8]
212212

213-
init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws {
213+
init(publicKeyObject: CBOR, algorithm: COSEAlgorithmIdentifier) throws(WebAuthnError) {
214214
self.algorithm = algorithm
215-
// Curve is key -1, or NegativeInt 0 for SwiftCBOR
216-
guard let curveRaw = publicKeyObject[.negativeInt(0)], case let .unsignedInt(curve) = curveRaw else {
217-
throw WebAuthnError.invalidCurve
218-
}
215+
/// Curve is key -1, or NegativeInt 0 for SwiftCBOR
216+
guard
217+
let curveRaw = publicKeyObject[.negativeInt(0)],
218+
case let .unsignedInt(curve) = curveRaw
219+
else { throw .invalidCurve }
219220
self.curve = curve
220-
// X Coordinate is key -2, or NegativeInt 1 for SwiftCBOR
221-
guard let xCoordRaw = publicKeyObject[.negativeInt(1)],
222-
case let .byteString(xCoordinateBytes) = xCoordRaw else {
223-
throw WebAuthnError.invalidXCoordinate
224-
}
221+
222+
/// X Coordinate is key -2, or NegativeInt 1 for SwiftCBOR
223+
guard
224+
let xCoordRaw = publicKeyObject[.negativeInt(1)],
225+
case let .byteString(xCoordinateBytes) = xCoordRaw
226+
else { throw .invalidXCoordinate }
225227
xCoordinate = xCoordinateBytes
226228
}
227229

228-
func verify(signature: some DataProtocol, data: some DataProtocol) throws {
230+
func verify(signature: some DataProtocol, data: some DataProtocol) throws(WebAuthnError) {
229231
throw WebAuthnError.unsupported
230232
}
231233
}

Sources/WebAuthn/WebAuthnError.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public struct WebAuthnError: Error, Hashable, Sendable {
5151
case leftOverBytesInAuthenticatorData
5252
case credentialIDTooLong
5353
case credentialIDTooShort
54+
case invalidPublicKeyLength
5455

5556
// MARK: CredentialPublicKey
5657
case badPublicKeyBytes
@@ -110,6 +111,7 @@ public struct WebAuthnError: Error, Hashable, Sendable {
110111
public static let leftOverBytesInAuthenticatorData = Self(reason: .leftOverBytesInAuthenticatorData)
111112
public static let credentialIDTooLong = Self(reason: .credentialIDTooLong)
112113
public static let credentialIDTooShort = Self(reason: .credentialIDTooShort)
114+
public static let invalidPublicKeyLength = Self(reason: .invalidPublicKeyLength)
113115

114116
// MARK: CredentialPublicKey
115117
public static let badPublicKeyBytes = Self(reason: .badPublicKeyBytes)

Sources/WebAuthn/WebAuthnManager.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,8 @@ public struct WebAuthnManager: Sendable {
112112
// TODO: Step 18. -> Verify client extensions
113113

114114
// Step 24.
115-
guard try await confirmCredentialIDNotRegisteredYet(parsedData.id.asString()) else {
116-
throw WebAuthnError.credentialIDAlreadyExists
117-
}
115+
guard try await confirmCredentialIDNotRegisteredYet(parsedData.id.asString())
116+
else { throw WebAuthnError.credentialIDAlreadyExists }
118117

119118
// Step 25.
120119
return Credential(
@@ -142,7 +141,7 @@ public struct WebAuthnManager: Sendable {
142141
timeout: Duration? = .seconds(60),
143142
allowCredentials: [PublicKeyCredentialDescriptor]? = nil,
144143
userVerification: UserVerificationRequirement = .preferred
145-
) throws -> PublicKeyCredentialRequestOptions {
144+
) -> PublicKeyCredentialRequestOptions {
146145
let challenge = challengeGenerator.generate()
147146

148147
return PublicKeyCredentialRequestOptions(

Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ final class WebAuthnManagerAuthenticationTests: XCTestCase {
3535

3636
func testBeginAuthentication() async throws {
3737
let allowCredentials = [PublicKeyCredentialDescriptor(type: .publicKey, id: [1, 0, 2, 30])]
38-
let options = try webAuthnManager.beginAuthentication(
38+
let options = webAuthnManager.beginAuthentication(
3939
timeout: .seconds(1234),
4040
allowCredentials: allowCredentials,
4141
userVerification: .preferred

Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ final class WebAuthnManagerIntegrationTests: XCTestCase {
9999
id: [UInt8](URLEncodedBase64(credential.id).urlDecoded.decoded!)
100100
)]
101101

102-
let authenticationOptions = try webAuthnManager.beginAuthentication(
102+
let authenticationOptions = webAuthnManager.beginAuthentication(
103103
timeout: authenticationTimeout,
104104
allowCredentials: rememberedCredentials,
105105
userVerification: userVerification

0 commit comments

Comments
 (0)