Skip to content

Commit 2987d0d

Browse files
committed
add more detailed errors
1 parent b91c8db commit 2987d0d

File tree

6 files changed

+83
-62
lines changed

6 files changed

+83
-62
lines changed

Sources/WebAuthn/Ceremonies/Registration/AuthenticatorAttestationResponse.swift

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,21 @@ struct ParsedAuthenticatorAttestationResponse {
3737
// Step 11. (assembling attestationObject)
3838
guard let attestationData = rawResponse.attestationObject.base64URLDecodedData,
3939
let decodedAttestationObject = try CBOR.decode([UInt8](attestationData)) else {
40-
throw WebAuthnError.cborDecodingAttestationDataFailed
40+
throw WebAuthnError.invalidAttestationData
4141
}
4242

43-
guard let authData = decodedAttestationObject["authData"], case let .byteString(authDataBytes) = authData else {
44-
throw WebAuthnError.authDataInvalidOrMissing
43+
guard let authData = decodedAttestationObject["authData"],
44+
case let .byteString(authDataBytes) = authData else {
45+
throw WebAuthnError.invalidAuthData
4546
}
46-
guard let formatCBOR = decodedAttestationObject["fmt"], case let .utf8String(format) = formatCBOR else {
47-
throw WebAuthnError.formatError
47+
guard let formatCBOR = decodedAttestationObject["fmt"],
48+
case let .utf8String(format) = formatCBOR,
49+
let attestationFormat = AttestationFormat(rawValue: format) else {
50+
throw WebAuthnError.invalidFmt
4851
}
4952

5053
guard let attestationStatement = decodedAttestationObject["attStmt"] else {
51-
throw WebAuthnError.missingAttestationFormat
52-
}
53-
54-
guard let attestationFormat = AttestationFormat(rawValue: format) else {
55-
throw WebAuthnError.unsupportedAttestationFormat
54+
throw WebAuthnError.invalidAttStmt
5655
}
5756

5857
attestationObject = AttestationObject(

Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ struct ParsedCredentialCreationResponse {
7373

7474
// Step 10.
7575
guard let clientData = raw.clientDataJSON.data(using: .utf8) else {
76-
throw WebAuthnError.hashingClientDataJSONFailed
76+
throw WebAuthnError.invalidClientDataJSON
7777
}
7878
let hash = SHA256.hash(data: clientData)
7979

@@ -88,7 +88,7 @@ struct ParsedCredentialCreationResponse {
8888

8989
// Step 23.
9090
guard rawID.count <= 1023 else {
91-
throw WebAuthnError.credentialIDTooBig
91+
throw WebAuthnError.credentialRawIDTooBig
9292
}
9393
}
9494
}

Sources/WebAuthn/Ceremonies/Shared/AuthenticatorData.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ extension AuthenticatorData {
6666
}
6767

6868
guard remainingCount == 0 else {
69-
throw WebAuthnError.leftOverBytes
69+
throw WebAuthnError.leftOverBytesInAuthenticatorData
7070
}
7171

7272
self.relyingPartyIDHash = relyingPartyIDHash

Sources/WebAuthn/Ceremonies/Shared/CredentialPublicKey.swift

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ enum CredentialPublicKey {
4040

4141
init(publicKeyBytes: [UInt8]) throws {
4242
guard let publicKeyObject = try CBOR.decode(publicKeyBytes) else {
43-
throw WebAuthnError.badRequestData
43+
throw WebAuthnError.badPublicKeyBytes
4444
}
4545

4646
// A leading 0x04 means we got a public key from an old U2F security key.
@@ -58,12 +58,12 @@ enum CredentialPublicKey {
5858
guard let keyTypeRaw = publicKeyObject[COSEKey.kty.cbor],
5959
case let .unsignedInt(keyTypeInt) = keyTypeRaw,
6060
let keyType = COSEKeyType(rawValue: keyTypeInt) else {
61-
throw WebAuthnError.badRequestData
61+
throw WebAuthnError.invalidKeyType
6262
}
6363

6464
guard let algorithmRaw = publicKeyObject[COSEKey.alg.cbor],
6565
case let .negativeInt(algorithmNegative) = algorithmRaw else {
66-
throw WebAuthnError.badRequestData
66+
throw WebAuthnError.invalidAlgorithm
6767
}
6868
// https://github.com/unrelentingtech/SwiftCBOR#swiftcbor
6969
// Negative integers are decoded as NegativeInt(UInt), where the actual number is -1 - i
@@ -114,18 +114,18 @@ struct EC2PublicKey: PublicKey {
114114
guard let curveRaw = publicKeyObject[COSEKey.crv.cbor],
115115
case let .unsignedInt(curve) = curveRaw,
116116
let coseCurve = COSECurve(rawValue: curve) else {
117-
throw WebAuthnError.badRequestData
117+
throw WebAuthnError.invalidCurve
118118
}
119119
self.curve = coseCurve
120120

121121
guard let xCoordRaw = publicKeyObject[COSEKey.x.cbor],
122122
case let .byteString(xCoordinateBytes) = xCoordRaw else {
123-
throw WebAuthnError.badRequestData
123+
throw WebAuthnError.invalidXCoordinate
124124
}
125125
xCoordinate = xCoordinateBytes
126126
guard let yCoordRaw = publicKeyObject[COSEKey.y.cbor],
127127
case let .byteString(yCoordinateBytes) = yCoordRaw else {
128-
throw WebAuthnError.badRequestData
128+
throw WebAuthnError.invalidYCoordinate
129129
}
130130
yCoordinate = yCoordinateBytes
131131
}
@@ -136,22 +136,22 @@ struct EC2PublicKey: PublicKey {
136136
let ecdsaSignature = try P256.Signing.ECDSASignature(derRepresentation: signature)
137137
guard try P256.Signing.PublicKey(rawRepresentation: rawRepresentation)
138138
.isValidSignature(ecdsaSignature, for: data) else {
139-
throw WebAuthnError.badRequestData
139+
throw WebAuthnError.invalidSignature
140140
}
141141
case .algES384:
142142
let ecdsaSignature = try P384.Signing.ECDSASignature(derRepresentation: signature)
143143
guard try P384.Signing.PublicKey(rawRepresentation: rawRepresentation)
144144
.isValidSignature(ecdsaSignature, for: data) else {
145-
throw WebAuthnError.badRequestData
145+
throw WebAuthnError.invalidSignature
146146
}
147147
case .algES512:
148148
let ecdsaSignature = try P521.Signing.ECDSASignature(derRepresentation: signature)
149149
guard try P521.Signing.PublicKey(rawRepresentation: rawRepresentation)
150150
.isValidSignature(ecdsaSignature, for: data) else {
151-
throw WebAuthnError.badRequestData
151+
throw WebAuthnError.invalidSignature
152152
}
153153
default:
154-
throw WebAuthnError.unsupportedCOSEAlgorithm
154+
throw WebAuthnError.unsupportedCOSEAlgorithmForEC2PublicKey
155155
}
156156
}
157157
}
@@ -170,13 +170,13 @@ struct RSAPublicKeyData: PublicKey {
170170

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

177177
guard let eRaw = publicKeyObject[COSEKey.e.cbor],
178178
case let .byteString(eBytes) = eRaw else {
179-
throw WebAuthnError.badRequestData
179+
throw WebAuthnError.invalidExponent
180180
}
181181
e = eBytes
182182
}
@@ -191,15 +191,15 @@ struct RSAPublicKeyData: PublicKey {
191191
case .algPS256, .algPS384, .algPS512:
192192
rsaPadding = .PSS
193193
default:
194-
throw WebAuthnError.badRequestData
194+
throw WebAuthnError.unsupportedCOSEAlgorithmForRSAPublicKey
195195
}
196196

197197
guard try _RSA.Signing.PublicKey(derRepresentation: rawRepresentation).isValidSignature(
198198
rsaSignature,
199199
for: data,
200200
padding: rsaPadding
201201
) else {
202-
throw WebAuthnError.badRequestData
202+
throw WebAuthnError.invalidSignature
203203
}
204204
}
205205
}
@@ -213,13 +213,13 @@ struct OKPPublicKey: PublicKey {
213213
self.algorithm = algorithm
214214
// Curve is key -1, or NegativeInt 0 for SwiftCBOR
215215
guard let curveRaw = publicKeyObject[.negativeInt(0)], case let .unsignedInt(curve) = curveRaw else {
216-
throw WebAuthnError.badRequestData
216+
throw WebAuthnError.invalidCurve
217217
}
218218
self.curve = curve
219219
// X Coordinate is key -2, or NegativeInt 1 for SwiftCBOR
220220
guard let xCoordRaw = publicKeyObject[.negativeInt(1)],
221221
case let .byteString(xCoordinateBytes) = xCoordRaw else {
222-
throw WebAuthnError.badRequestData
222+
throw WebAuthnError.invalidXCoordinate
223223
}
224224
xCoordinate = xCoordinateBytes
225225
}

Sources/WebAuthn/WebAuthnError.swift

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,56 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
public enum WebAuthnError: Error {
16-
case authDataTooShort
17-
case extensionDataMissing
18-
case leftOverBytes
19-
20-
case attestedCredentialFlagNotSet
21-
case userPresentFlagNotSet
22-
case userVerificationRequiredButFlagNotSet
23-
16+
// MARK: Shared
17+
case invalidClientDataJSON
2418
case attestedCredentialDataMissing
25-
case badRequestData
26-
case validationError
27-
case formatError
28-
case hashingClientDataJSONFailed
2919
case relyingPartyIDHashDoesNotMatch
20+
case userPresentFlagNotSet
21+
case invalidSignature
22+
23+
// MARK: AttestationObject
24+
case userVerificationRequiredButFlagNotSet
3025
case attestationStatementMissing
31-
case missingAttestedCredentialData
32-
case missingAttestationFormat
26+
case attestationVerificationNotSupported
3327

34-
case credentialIDTooBig
28+
// MARK: WebAuthnManager
29+
case invalidUserID
30+
case unsupportedCredentialPublicKeyAlgorithm
3531
case credentialIDAlreadyExists
32+
case invalidAuthenticatorData
33+
case invalidRelyingPartyID
34+
case userVerifiedFlagNotSet
35+
case potentialReplayAttack
36+
case invalidAssertionCredentialType
37+
38+
// MARK: ParsedAuthenticatorAttestationResponse
39+
case invalidAttestationData
40+
case invalidAuthData
41+
case invalidFmt
42+
case invalidAttStmt
43+
case attestationFormatNotSupported
3644

45+
// MARK: ParsedCredentialCreationResponse
3746
case invalidRawID
3847
case invalidCredentialCreationType
39-
case invalidClientDataJSON
40-
case cborDecodingAttestationDataFailed
41-
case authDataInvalidOrMissing
48+
case credentialRawIDTooBig
4249

43-
case unsupportedCOSEAlgorithm
44-
case unsupportedCredentialPublicKeyAlgorithm
45-
case unsupportedAttestationFormat
50+
// MARK: AuthenticatorData
51+
case authDataTooShort
52+
case attestedCredentialFlagNotSet
53+
case extensionDataMissing
54+
case leftOverBytesInAuthenticatorData
4655

47-
case attestationVerificationNotSupported
56+
// MARK: CredentialPublicKey
57+
case badPublicKeyBytes
58+
case invalidKeyType
59+
case invalidAlgorithm
60+
case invalidCurve
61+
case invalidXCoordinate
62+
case invalidYCoordinate
63+
case unsupportedCOSEAlgorithm
64+
case unsupportedCOSEAlgorithmForEC2PublicKey
65+
case invalidModulus
66+
case invalidExponent
67+
case unsupportedCOSEAlgorithmForRSAPublicKey
4868
}

Sources/WebAuthn/WebAuthnManager.swift

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public struct WebAuthnManager {
2727
/// Generate a new set of registration data to be sent to the client and authenticator.
2828
public func beginRegistration(user: User) throws -> PublicKeyCredentialCreationOptions {
2929
guard let base64ID = user.userID.data(using: .utf8)?.base64EncodedString() else {
30-
throw WebAuthnError.badRequestData
30+
throw WebAuthnError.invalidUserID
3131
}
3232

3333
let userEntity = PublicKeyCredentialUserEntity(name: user.name, id: base64ID, displayName: user.displayName)
@@ -68,7 +68,7 @@ public struct WebAuthnManager {
6868
)
6969

7070
guard let attestedData = parsedData.response.attestationObject.authenticatorData.attestedData else {
71-
throw WebAuthnError.missingAttestedCredentialData
71+
throw WebAuthnError.attestedCredentialDataMissing
7272
}
7373

7474
// Step 17.
@@ -126,12 +126,12 @@ public struct WebAuthnManager {
126126
requireUserVerification: Bool = false
127127
) throws -> VerifiedAuthentication {
128128
let expectedRpID = config.relyingPartyID
129-
guard credential.type == "public-key" else { throw WebAuthnError.badRequestData }
129+
guard credential.type == "public-key" else { throw WebAuthnError.invalidAssertionCredentialType }
130130

131131
let response = credential.response
132132

133133
guard let clientDataData = response.clientDataJSON.base64URLDecodedData else {
134-
throw WebAuthnError.badRequestData
134+
throw WebAuthnError.invalidClientDataJSON
135135
}
136136
let clientData = try JSONDecoder().decode(CollectedClientData.self, from: clientDataData)
137137
try clientData.verify(
@@ -142,32 +142,34 @@ public struct WebAuthnManager {
142142
// TODO: - Verify token binding
143143

144144
guard let authenticatorDataBytes = response.authenticatorData.base64URLDecodedData else {
145-
throw WebAuthnError.badRequestData
145+
throw WebAuthnError.invalidAuthenticatorData
146146
}
147147
let authenticatorData = try AuthenticatorData(bytes: authenticatorDataBytes)
148148

149-
guard let expectedRpIDData = expectedRpID.data(using: .utf8) else { throw WebAuthnError.badRequestData }
149+
guard let expectedRpIDData = expectedRpID.data(using: .utf8) else { throw WebAuthnError.invalidRelyingPartyID }
150150
let expectedRpIDHash = SHA256.hash(data: expectedRpIDData)
151-
guard expectedRpIDHash == authenticatorData.relyingPartyIDHash else { throw WebAuthnError.badRequestData }
151+
guard expectedRpIDHash == authenticatorData.relyingPartyIDHash else {
152+
throw WebAuthnError.relyingPartyIDHashDoesNotMatch
153+
}
152154

153-
guard authenticatorData.flags.userPresent else { throw WebAuthnError.badRequestData }
155+
guard authenticatorData.flags.userPresent else { throw WebAuthnError.userPresentFlagNotSet }
154156
if requireUserVerification {
155-
guard authenticatorData.flags.userVerified else { throw WebAuthnError.badRequestData }
157+
guard authenticatorData.flags.userVerified else { throw WebAuthnError.userVerifiedFlagNotSet }
156158
}
157159

158160
if authenticatorData.counter > 0 || credentialCurrentSignCount > 0 {
159161
guard authenticatorData.counter > credentialCurrentSignCount else {
160162
// This is a signal that the authenticator may be cloned, i.e. at least two copies of the credential
161163
// private key may exist and are being used in parallel.
162-
throw WebAuthnError.badRequestData
164+
throw WebAuthnError.potentialReplayAttack
163165
}
164166
}
165167

166168
let clientDataHash = SHA256.hash(data: clientDataData)
167169
let signatureBase = authenticatorDataBytes + clientDataHash
168170

169171
let credentialPublicKey = try CredentialPublicKey(publicKeyBytes: credentialPublicKey)
170-
guard let signatureData = response.signature.base64URLDecodedData else { throw WebAuthnError.badRequestData }
172+
guard let signatureData = response.signature.base64URLDecodedData else { throw WebAuthnError.invalidSignature }
171173
try credentialPublicKey.verify(signature: signatureData, data: signatureBase)
172174

173175
return VerifiedAuthentication(

0 commit comments

Comments
 (0)