Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make code sort of work with some base64 URL decoding. #1

Merged
merged 1 commit into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Sources/App/routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ func routes(_ app: Application) throws {
let user = User(id: userID, username: username)
try await user.save(on: req.db)

let credential = WebAuthnCredential(id: credential.credentialID, publicKey: credential.publicKey.pemRepresentation, userID: userID)
try await credential.save(on: req.db)
let webAuthnCredential = WebAuthnCredential(id: credential.credentialID, publicKey: credential.publicKey.pemRepresentation, userID: userID)
try await webAuthnCredential.save(on: req.db)

req.auth.login(user)

Expand All @@ -76,7 +76,7 @@ func routes(_ app: Application) throws {
req.logger.debug("Authenticate Challenge is \(encodedChallenge)")
req.session.data["challenge"] = encodedChallenge
req.session.data["userID"] = try user.requireID().uuidString
let credentials = try await user.$credentials.get(on: req.db).map {
let credentials: [WebAuthnCredential] = try await user.$credentials.get(on: req.db).map {
// We need to convert the IDs to base64 encoded from base64 URL encoded
var id = $0.id!.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
while id.count % 4 != 0 {
Expand Down
33 changes: 16 additions & 17 deletions Sources/WebAuthn/WebAuthn.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ import Crypto
import Logging
import Foundation

extension String {
var base64URLDecodedData: Data? {
var result = self.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
while result.count % 4 != 0 {
result = result.appending("=")
}
return Data(base64Encoded: result)
}
}

public enum WebAuthn {
public static func validateAssertion(_ data: AssertionCredential, challengeProvided: String, publicKey: P256.Signing.PublicKey, logger: Logger) throws {
guard let clientObjectData = Data(base64Encoded: data.response.clientDataJSON) else {
guard let clientObjectData = data.response.clientDataJSON.base64URLDecodedData else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll need to check the spec here. In my testing the assertion was not base64 USL decoded, so it may depend on the assertion and we might need to check some data to see which path to go down

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep it's URL encoded, thanks!

throw WebAuthnError.badRequestData
}
let clientObject = try JSONDecoder().decode(ClientDataObject.self, from: clientObjectData)
Expand All @@ -14,20 +24,12 @@ public enum WebAuthn {
}
let clientDataJSONHash = SHA256.hash(data: clientObjectData)

var base64AssertionString = data.response.authenticatorData.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
while base64AssertionString.count % 4 != 0 {
base64AssertionString = base64AssertionString.appending("=")
}
guard let authenticatorData = Data(base64Encoded: base64AssertionString) else {
guard let authenticatorData = data.response.authenticatorData.base64URLDecodedData else {
throw WebAuthnError.badRequestData
}
let signedData = authenticatorData + clientDataJSONHash

var base64SignatureString = data.response.signature.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
while base64SignatureString.count % 4 != 0 {
base64SignatureString = base64SignatureString.appending("=")
}
guard let signatureData = Data(base64Encoded: base64SignatureString) else {
guard let signatureData = data.response.signature.base64URLDecodedData else {
throw WebAuthnError.badRequestData
}
let signature = try P256.Signing.ECDSASignature(derRepresentation: signatureData)
Expand All @@ -37,7 +39,7 @@ public enum WebAuthn {
}

public static func parseRegisterCredentials(_ data: RegisterWebAuthnCredentialData, challengeProvided: String, origin: String, logger: Logger) throws -> Credential {
guard let clientObjectData = Data(base64Encoded: data.response.clientDataJSON) else {
guard let clientObjectData = data.response.clientDataJSON.base64URLDecodedData else {
throw WebAuthnError.badRequestData
}
let clientObject = try JSONDecoder().decode(ClientDataObject.self, from: clientObjectData)
Expand All @@ -50,11 +52,8 @@ public enum WebAuthn {
guard origin == clientObject.origin else {
throw WebAuthnError.validationError
}
var base64AttestationString = data.response.attestationObject.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
while base64AttestationString.count % 4 != 0 {
base64AttestationString = base64AttestationString.appending("=")
}
guard let attestationData = Data(base64Encoded: base64AttestationString) else {

guard let attestationData = data.response.attestationObject.base64URLDecodedData else {
throw WebAuthnError.badRequestData
}
guard let decodedAttestationObject = try CBOR.decode([UInt8](attestationData)) else {
Expand Down