@@ -18,21 +18,36 @@ import Logging
18
18
import Foundation
19
19
20
20
public enum WebAuthn {
21
- public static func validateAssertion( _ data: AssertionCredential , challengeProvided: String , publicKey: P256 . Signing . PublicKey , logger: Logger ) throws {
21
+ /// Verify that the user has legitimately completed the login process
22
+ ///
23
+ /// - Parameters:
24
+ /// - data: The response to verify
25
+ /// - expectedChallenge: The expected base64url-encoded challenge
26
+ /// - publicKey: The users public key
27
+ /// - logger: A logger
28
+ /// - Throws:
29
+ /// - An error if the authentication response isn't valid
30
+ public static func verifyAuthenticationResponse(
31
+ _ data: AuthenticationResponse ,
32
+ expectedChallenge: String ,
33
+ publicKey: P256 . Signing . PublicKey ,
34
+ // requireUserVerification: Bool = false
35
+ logger: Logger
36
+ ) throws {
22
37
guard let clientObjectData = data. response. clientDataJSON. base64URLDecodedData else {
23
38
throw WebAuthnError . badRequestData
24
39
}
25
40
let clientObject = try JSONDecoder ( ) . decode ( ClientDataObject . self, from: clientObjectData)
26
- guard challengeProvided == clientObject. challenge else {
41
+ guard expectedChallenge == clientObject. challenge else {
27
42
throw WebAuthnError . validationError
28
43
}
29
44
let clientDataJSONHash = SHA256 . hash ( data: clientObjectData)
30
-
45
+
31
46
guard let authenticatorData = data. response. authenticatorData. base64URLDecodedData else {
32
47
throw WebAuthnError . badRequestData
33
48
}
34
49
let signedData = authenticatorData + clientDataJSONHash
35
-
50
+
36
51
guard let signatureData = data. response. signature. base64URLDecodedData else {
37
52
throw WebAuthnError . badRequestData
38
53
}
@@ -41,8 +56,13 @@ public enum WebAuthn {
41
56
throw WebAuthnError . validationError
42
57
}
43
58
}
44
-
45
- public static func parseRegisterCredentials( _ data: RegisterWebAuthnCredentialData , challengeProvided: String , origin: String , logger: Logger ) throws -> Credential {
59
+
60
+ public static func parseRegisterCredentials(
61
+ _ data: RegistrationResponse ,
62
+ challengeProvided: String ,
63
+ origin: String ,
64
+ logger: Logger
65
+ ) throws -> Credential {
46
66
guard let clientObjectData = data. response. clientDataJSON. base64URLDecodedData else {
47
67
throw WebAuthnError . badRequestData
48
68
}
@@ -64,7 +84,7 @@ public enum WebAuthn {
64
84
throw WebAuthnError . badRequestData
65
85
}
66
86
logger. debug ( " Got COBR decoded data: \( decodedAttestationObject) " )
67
-
87
+
68
88
// Ignore format/statement for now
69
89
guard let authData = decodedAttestationObject [ " authData " ] , case let . byteString( authDataBytes) = authData else {
70
90
throw WebAuthnError . badRequestData
@@ -86,11 +106,11 @@ public enum WebAuthn {
86
106
// https://github.com/unrelentingtech/SwiftCBOR#swiftcbor
87
107
// Negative integers are decoded as NegativeInt(UInt), where the actual number is -1 - i
88
108
let algorithm : Int = - 1 - Int( algorithmNegative)
89
-
109
+
90
110
// Curve is key -1 - or -0 for SwiftCBOR
91
111
// X Coordinate is key -2, or NegativeInt 1 for SwiftCBOR
92
112
// Y Coordinate is key -3, or NegativeInt 2 for SwiftCBOR
93
-
113
+
94
114
guard let curveRaw = publicKeyObject [ . negativeInt( 0 ) ] , case let . unsignedInt( curve) = curveRaw else {
95
115
throw WebAuthnError . badRequestData
96
116
}
@@ -100,54 +120,60 @@ public enum WebAuthn {
100
120
guard let yCoordRaw = publicKeyObject [ . negativeInt( 2 ) ] , case let . byteString( yCoordinateBytes) = yCoordRaw else {
101
121
throw WebAuthnError . badRequestData
102
122
}
103
-
123
+
104
124
logger. debug ( " Key type was \( keyType) " )
105
125
logger. debug ( " Algorithm was \( algorithm) " )
106
126
logger. debug ( " Curve was \( curve) " )
107
-
127
+
108
128
let key = try P256 . Signing. PublicKey ( rawRepresentation: xCoordinateBytes + yCoordinateBytes)
109
129
logger. debug ( " Key is \( key. pemRepresentation) " )
110
-
130
+
111
131
return Credential ( credentialID: data. id, publicKey: key)
112
132
}
113
-
114
- static func parseAttestedData( _ data: [ UInt8 ] , logger: Logger ) throws -> AttestedCredentialData {
133
+
134
+ static func parseAttestedData(
135
+ _ data: [ UInt8 ] ,
136
+ logger: Logger
137
+ ) throws -> AttestedCredentialData {
115
138
// We've parsed the first 37 bytes so far, the next bytes now should be the attested credential data
116
139
// See https://w3c.github.io/webauthn/#sctn-attested-credential-data
117
140
let aaguidLength = 16
118
141
let aaguid = data [ 37 ..< ( 37 + aaguidLength) ] // To byte at index 52
119
-
142
+
120
143
let idLengthBytes = data [ 53 ..< 55 ] // Length is 2 bytes
121
144
let idLengthData = Data ( idLengthBytes)
122
145
let idLength : UInt16 = idLengthData. toInteger ( endian: . big)
123
146
let credentialIDEndIndex = Int ( idLength) + 55
124
-
147
+
125
148
let credentialID = data [ 55 ..< credentialIDEndIndex]
126
149
let publicKeyBytes = data [ credentialIDEndIndex... ]
127
-
150
+
128
151
return AttestedCredentialData ( aaguid: Array ( aaguid) , credentialID: Array ( credentialID) , publicKey: Array ( publicKeyBytes) )
129
152
}
130
-
131
- static func parseAttestationObject( _ bytes: [ UInt8 ] , logger: Logger ) throws -> AttestedCredentialData ? {
153
+
154
+ static func parseAttestationObject(
155
+ _ bytes: [ UInt8 ] ,
156
+ logger: Logger
157
+ ) throws -> AttestedCredentialData ? {
132
158
let minAuthDataLength = 37
133
159
let minAttestedAuthLength = 55
134
160
// TODO - fix
135
161
// let maxCredentialIDLength = 1023
136
162
// What to do when we don't have this
137
163
var credentialsData : AttestedCredentialData ? = nil
138
-
164
+
139
165
guard bytes. count >= minAuthDataLength else {
140
166
throw WebAuthnError . authDataTooShort
141
167
}
142
-
168
+
143
169
// TODO: Use
144
170
// let rpIDHashData = bytes[..<32]
145
171
let flags = AuthenticatorFlags ( bytes [ 32 ] )
146
172
// TODO: Use
147
173
// let counter: UInt32 = Data(bytes[33..<37]).toInteger(endian: .big)
148
-
174
+
149
175
var remainingCount = bytes. count - minAuthDataLength
150
-
176
+
151
177
if flags. attestedCredentialData {
152
178
guard bytes. count > minAttestedAuthLength else {
153
179
throw WebAuthnError . attestedCredentialDataMissing
@@ -162,15 +188,15 @@ public enum WebAuthn {
162
188
throw WebAuthnError . attestedCredentialFlagNotSet
163
189
}
164
190
}
165
-
191
+
166
192
if flags. extensionDataIncluded {
167
193
guard remainingCount != 0 else {
168
194
throw WebAuthnError . extensionDataMissing
169
195
}
170
196
let extensionData = bytes [ ( bytes. count - remainingCount) ... ]
171
197
remainingCount -= extensionData. count
172
198
}
173
-
199
+
174
200
guard remainingCount == 0 else {
175
201
throw WebAuthnError . leftOverBytes
176
202
}
0 commit comments