Skip to content

Commit 4264039

Browse files
committed
Seperate type for base64 data
1 parent 07359c8 commit 4264039

File tree

6 files changed

+85
-28
lines changed

6 files changed

+85
-28
lines changed

Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import Foundation
1616

1717
/// The `PublicKeyCredentialRequestOptions` gets passed to the WebAuthn API (`navigator.credentials.get()`)
1818
public struct PublicKeyCredentialRequestOptions: Codable {
19-
public let challenge: String
19+
public let challenge: EncodedBase64
2020
public let timeout: TimeInterval?
2121
public let rpId: String?
2222
public let allowCredentials: [PublicKeyCredentialDescriptor]?

Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ struct ParsedCredentialCreationResponse {
7272
)
7373

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

Sources/WebAuthn/Helpers/Base64Utilities.swift

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,53 +15,103 @@
1515
import Foundation
1616
import Logging
1717

18-
public typealias URLEncodedBase64 = String
19-
public typealias EncodedBase64 = String
18+
/// Container for URL encoded base64 data
19+
public struct URLEncodedBase64: ExpressibleByStringLiteral, Codable, Hashable {
20+
let string: String
21+
22+
public init(_ string: String) {
23+
self.string = string
24+
}
25+
26+
public init(stringLiteral value: StringLiteralType) {
27+
self.init(value)
28+
}
29+
30+
public init(from decoder: Decoder) throws {
31+
let container = try decoder.singleValueContainer()
32+
self.string = try container.decode(String.self)
33+
}
34+
35+
public func encode(to encoder: Encoder) throws {
36+
var container = encoder.singleValueContainer()
37+
try container.encode(self.string)
38+
}
39+
}
40+
41+
/// Container for base64 encoded data
42+
public struct EncodedBase64: ExpressibleByStringLiteral, Codable, Hashable {
43+
let string: String
44+
45+
public init(_ string: String) {
46+
self.string = string
47+
}
48+
49+
public init(stringLiteral value: StringLiteralType) {
50+
self.init(value)
51+
}
52+
53+
public init(from decoder: Decoder) throws {
54+
let container = try decoder.singleValueContainer()
55+
self.string = try container.decode(String.self)
56+
}
57+
58+
public func encode(to encoder: Encoder) throws {
59+
var container = encoder.singleValueContainer()
60+
try container.encode(self.string)
61+
}
62+
}
63+
64+
//public typealias URLEncodedBase64 = String
65+
//public typealias EncodedBase64 = String
2066

2167
extension Array where Element == UInt8 {
2268
/// Encodes an array of bytes into a base64url-encoded string
2369
/// - Returns: A base64url-encoded string
24-
public func base64URLEncodedString() -> String {
70+
public func base64URLEncodedString() -> URLEncodedBase64 {
2571
let base64String = Data(bytes: self, count: self.count).base64EncodedString()
26-
return String.base64URL(fromBase64: base64String)
72+
return String.base64URL(fromBase64: .init(base64String))
2773
}
2874

2975
/// Encodes an array of bytes into a base64 string
3076
/// - Returns: A base64-encoded string
31-
public func base64EncodedString() -> String {
32-
return Data(bytes: self, count: self.count).base64EncodedString()
77+
public func base64EncodedString() -> EncodedBase64 {
78+
return .init(Data(bytes: self, count: self.count).base64EncodedString())
3379
}
3480
}
3581

3682
extension Data {
3783
/// Encodes data into a base64url-encoded string
3884
/// - Returns: A base64url-encoded string
39-
public func base64URLEncodedString() -> String {
85+
public func base64URLEncodedString() -> URLEncodedBase64 {
4086
return [UInt8](self).base64URLEncodedString()
4187
}
4288
}
4389

4490
extension String {
4591
/// Decode a base64url-encoded `String` to a base64 `String`
4692
/// - Returns: A base64-encoded `String`
47-
public static func base64(fromBase64URLEncoded base64URLEncoded: String) -> Self {
48-
return base64URLEncoded.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
93+
public static func base64(fromBase64URLEncoded base64URLEncoded: URLEncodedBase64) -> EncodedBase64 {
94+
return .init(
95+
base64URLEncoded.string.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
96+
)
4997
}
5098

51-
public static func base64URL(fromBase64 base64Encoded: String) -> Self {
52-
return base64Encoded.replacingOccurrences(of: "+", with: "-")
53-
.replacingOccurrences(of: "/", with: "_")
54-
.replacingOccurrences(of: "=", with: "")
99+
public static func base64URL(fromBase64 base64Encoded: EncodedBase64) -> URLEncodedBase64 {
100+
return .init(
101+
base64Encoded.string.replacingOccurrences(of: "+", with: "-")
102+
.replacingOccurrences(of: "/", with: "_")
103+
.replacingOccurrences(of: "=", with: "")
104+
)
55105
}
56106

57-
func toBase64() -> String {
58-
return Data(self.utf8).base64EncodedString()
107+
func toBase64() -> EncodedBase64 {
108+
return .init(Data(self.utf8).base64EncodedString())
59109
}
60110
}
61111

62-
extension String {
112+
extension URLEncodedBase64 {
63113
public var base64URLDecodedData: Data? {
64-
var result = self.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
114+
var result = self.string.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
65115
while result.count % 4 != 0 {
66116
result = result.appending("=")
67117
}

Sources/WebAuthn/WebAuthnManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public struct WebAuthnManager {
104104
}
105105

106106
public func beginAuthentication(
107-
challenge: String? = nil,
107+
challenge: EncodedBase64? = nil,
108108
timeout: TimeInterval?,
109109
allowCredentials: [PublicKeyCredentialDescriptor]? = nil,
110110
userVerification: UserVerificationRequirement = .preferred,

Tests/WebAuthnTests/HelpersTests.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ final class HelpersTests: XCTestCase {
1111
let base64Encoded = input.base64EncodedString()
1212
let base64URLEncoded = input.base64URLEncodedString()
1313

14-
XCTAssertEqual(expectedBase64, base64Encoded)
15-
XCTAssertEqual(expectedBase64URL, base64URLEncoded)
14+
XCTAssertEqual(expectedBase64, base64Encoded.string)
15+
XCTAssertEqual(expectedBase64URL, base64URLEncoded.string)
16+
}
17+
18+
func testEncodeBase64Codable() throws {
19+
let base64 = EncodedBase64("AQABAAEBAAEAAQEAAAABAA==")
20+
let json = try JSONEncoder().encode(base64)
21+
let decodedBase64 = try JSONDecoder().decode(EncodedBase64.self, from: json)
22+
XCTAssertEqual(base64, decodedBase64)
1623
}
1724
}

Tests/WebAuthnTests/WebAuthnManagerTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ final class WebAuthnManagerTests: XCTestCase {
4949
XCTAssertEqual(options.relyingParty.id, relyingPartyID)
5050
XCTAssertEqual(options.relyingParty.name, relyingPartyDisplayName)
5151
XCTAssertEqual(options.timeout, timeout)
52-
XCTAssertEqual(options.user.id, user.userID.toBase64())
52+
XCTAssertEqual(options.user.id, user.userID.toBase64().string)
5353
XCTAssertEqual(options.user.displayName, user.displayName)
5454
XCTAssertEqual(options.user.name, user.name)
5555
XCTAssertEqual(options.publicKeyCredentialParameters, [publicKeyCredentialParameter])
@@ -340,18 +340,18 @@ final class WebAuthnManagerTests: XCTestCase {
340340

341341
func testFinishRegistrationFailsIfRawIDIsTooLong() async throws {
342342
try await assertThrowsError(
343-
await finishRegistration(rawID: [UInt8](repeating: 0, count: 1024).base64EncodedString()),
343+
await finishRegistration(rawID: String.base64URL(fromBase64: [UInt8](repeating: 0, count: 1024).base64EncodedString())),
344344
expect: WebAuthnError.credentialRawIDTooLong
345345
)
346346
}
347347

348348
private func finishRegistration(
349349
challenge: EncodedBase64 = "cmFuZG9tU3RyaW5nRnJvbVNlcnZlcg",
350-
id: EncodedBase64 = "4PrJNQUJ9xdI2DeCzK9rTBRixhXHDiVdoTROQIh8j80",
350+
id: String = "4PrJNQUJ9xdI2DeCzK9rTBRixhXHDiVdoTROQIh8j80",
351351
type: String = "public-key",
352-
rawID: EncodedBase64 = "4PrJNQUJ9xdI2DeCzK9rTBRixhXHDiVdoTROQIh8j80",
353-
clientDataJSON: String = "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiY21GdVpHOXRVM1J5YVc1blJuSnZiVk5sY25abGNnIiwib3JpZ2luIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9",
354-
attestationObject: String = "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVg5o3mm9u6vuaVeN4wRgDTidR5oL6ufLTCrE9ISVYbOGUdBAAAAAKN5pvbur7mlXjeMEYA04nUAAQAA",
352+
rawID: URLEncodedBase64 = "4PrJNQUJ9xdI2DeCzK9rTBRixhXHDiVdoTROQIh8j80",
353+
clientDataJSON: URLEncodedBase64 = "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiY21GdVpHOXRVM1J5YVc1blJuSnZiVk5sY25abGNnIiwib3JpZ2luIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsImNyb3NzT3JpZ2luIjpmYWxzZSwib3RoZXJfa2V5c19jYW5fYmVfYWRkZWRfaGVyZSI6ImRvIG5vdCBjb21wYXJlIGNsaWVudERhdGFKU09OIGFnYWluc3QgYSB0ZW1wbGF0ZS4gU2VlIGh0dHBzOi8vZ29vLmdsL3lhYlBleCJ9",
354+
attestationObject: URLEncodedBase64 = "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVg5o3mm9u6vuaVeN4wRgDTidR5oL6ufLTCrE9ISVYbOGUdBAAAAAKN5pvbur7mlXjeMEYA04nUAAQAA",
355355
requireUserVerification: Bool = false,
356356
confirmCredentialIDNotRegisteredYet: (String) async throws -> Bool = { _ in true }
357357
) async throws -> Credential {

0 commit comments

Comments
 (0)