Skip to content

Commit 1fa7a47

Browse files
Updated public transfer types to be fully mutable and codable in either direction (#89)
Co-authored-by: Dimitri Bouniol <[email protected]>
1 parent a1e7ccd commit 1fa7a47

7 files changed

+148
-25
lines changed

Sources/WebAuthn/Ceremonies/Authentication/AuthenticationCredential.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public struct AuthenticationCredential: Sendable {
3434
public let type: CredentialType
3535
}
3636

37-
extension AuthenticationCredential: Decodable {
37+
extension AuthenticationCredential: Codable {
3838
public init(from decoder: any Decoder) throws {
3939
let container = try decoder.container(keyedBy: CodingKeys.self)
4040

@@ -44,6 +44,17 @@ extension AuthenticationCredential: Decodable {
4444
authenticatorAttachment = try container.decodeIfPresent(AuthenticatorAttachment.self, forKey: .authenticatorAttachment)
4545
type = try container.decode(CredentialType.self, forKey: .type)
4646
}
47+
48+
public func encode(to encoder: any Encoder) throws {
49+
var container = encoder.container(keyedBy: CodingKeys.self)
50+
51+
try container.encode(id, forKey: .id)
52+
try container.encode(rawID.base64URLEncodedString(), forKey: .rawID)
53+
try container.encode(response, forKey: .response)
54+
try container.encodeIfPresent(authenticatorAttachment, forKey: .authenticatorAttachment)
55+
try container.encode(type, forKey: .type)
56+
}
57+
4758

4859
private enum CodingKeys: String, CodingKey {
4960
case id

Sources/WebAuthn/Ceremonies/Authentication/AuthenticatorAssertionResponse.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public struct AuthenticatorAssertionResponse: Sendable {
4848
public let attestationObject: [UInt8]?
4949
}
5050

51-
extension AuthenticatorAssertionResponse: Decodable {
51+
extension AuthenticatorAssertionResponse: Codable {
5252
public init(from decoder: any Decoder) throws {
5353
let container = try decoder.container(keyedBy: CodingKeys.self)
5454

@@ -58,6 +58,17 @@ extension AuthenticatorAssertionResponse: Decodable {
5858
userHandle = try container.decodeBytesFromURLEncodedBase64IfPresent(forKey: .userHandle)
5959
attestationObject = try container.decodeBytesFromURLEncodedBase64IfPresent(forKey: .attestationObject)
6060
}
61+
62+
public func encode(to encoder: any Encoder) throws {
63+
var container = encoder.container(keyedBy: CodingKeys.self)
64+
65+
try container.encode(clientDataJSON.base64URLEncodedString(), forKey: .clientDataJSON)
66+
try container.encode(authenticatorData.base64URLEncodedString(), forKey: .authenticatorData)
67+
try container.encode(signature.base64URLEncodedString(), forKey: .signature)
68+
try container.encodeIfPresent(userHandle?.base64URLEncodedString(), forKey: .userHandle)
69+
try container.encodeIfPresent(attestationObject?.base64URLEncodedString(), forKey: .attestationObject)
70+
}
71+
6172

6273
private enum CodingKeys: String, CodingKey {
6374
case clientDataJSON

Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,30 @@ import Foundation
1818
/// When encoding using `Encodable`, the byte arrays are encoded as base64url.
1919
///
2020
/// - SeeAlso: https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options
21-
public struct PublicKeyCredentialRequestOptions: Encodable, Sendable {
21+
public struct PublicKeyCredentialRequestOptions: Codable, Sendable {
2222
/// A challenge that the authenticator signs, along with other data, when producing an authentication assertion
2323
///
2424
/// When encoding using `Encodable` this is encoded as base64url.
25-
public let challenge: [UInt8]
25+
public var challenge: [UInt8]
2626

2727
/// A time, in seconds, that the caller is willing to wait for the call to complete. This is treated as a
2828
/// hint, and may be overridden by the client.
2929
///
3030
/// - Note: When encoded, this value is represented in milleseconds as a ``UInt32``.
3131
/// See https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options
32-
public let timeout: Duration?
32+
public var timeout: Duration?
3333

3434
/// The ID of the Relying Party making the request.
3535
///
3636
/// This is configured on ``WebAuthnManager`` before its ``WebAuthnManager/beginAuthentication(timeout:allowCredentials:userVerification:)`` method is called.
3737
/// - Note: When encoded, this field appears as `rpId` to match the expectations of `navigator.credentials.get()`.
38-
public let relyingPartyID: String
38+
public var relyingPartyID: String
3939

4040
/// Optionally used by the client to find authenticators eligible for this authentication ceremony.
41-
public let allowCredentials: [PublicKeyCredentialDescriptor]?
41+
public var allowCredentials: [PublicKeyCredentialDescriptor]?
4242

4343
/// Specifies whether the user should be verified during the authentication ceremony.
44-
public let userVerification: UserVerificationRequirement?
44+
public var userVerification: UserVerificationRequirement?
4545

4646
// let extensions: [String: Any]
4747

@@ -50,15 +50,42 @@ public struct PublicKeyCredentialRequestOptions: Encodable, Sendable {
5050

5151
try container.encode(challenge.base64URLEncodedString(), forKey: .challenge)
5252
try container.encodeIfPresent(timeout?.milliseconds, forKey: .timeout)
53-
try container.encode(relyingPartyID, forKey: .rpID)
53+
try container.encode(relyingPartyID, forKey: .relyingPartyID)
5454
try container.encodeIfPresent(allowCredentials, forKey: .allowCredentials)
5555
try container.encodeIfPresent(userVerification, forKey: .userVerification)
5656
}
57+
58+
public init(
59+
challenge: [UInt8],
60+
timeout: Duration?,
61+
relyingPartyID: String,
62+
allowCredentials: [PublicKeyCredentialDescriptor]?,
63+
userVerification: UserVerificationRequirement?
64+
) {
65+
self.challenge = challenge
66+
self.timeout = timeout
67+
self.relyingPartyID = relyingPartyID
68+
self.allowCredentials = allowCredentials
69+
self.userVerification = userVerification
70+
}
71+
72+
public init(from decoder: any Decoder) throws {
73+
let values = try decoder.container(keyedBy: CodingKeys.self)
74+
75+
self.challenge = try values.decodeBytesFromURLEncodedBase64(forKey: .challenge)
76+
77+
if let timeout = try values.decodeIfPresent(UInt32.self, forKey: .timeout) {
78+
self.timeout = .milliseconds(timeout)
79+
}
80+
self.relyingPartyID = try values.decode(String.self, forKey: .relyingPartyID)
81+
self.allowCredentials = try values.decodeIfPresent([PublicKeyCredentialDescriptor].self, forKey: .allowCredentials)
82+
self.userVerification = try values.decodeIfPresent(UserVerificationRequirement.self, forKey: .userVerification)
83+
}
5784

5885
private enum CodingKeys: String, CodingKey {
5986
case challenge
6087
case timeout
61-
case rpID = "rpId"
88+
case relyingPartyID = "rpId"
6289
case allowCredentials
6390
case userVerification
6491
}
@@ -67,7 +94,7 @@ public struct PublicKeyCredentialRequestOptions: Encodable, Sendable {
6794
/// Information about a generated credential.
6895
///
6996
/// When encoding using `Encodable`, `id` is encoded as base64url.
70-
public struct PublicKeyCredentialDescriptor: Equatable, Encodable, Sendable {
97+
public struct PublicKeyCredentialDescriptor: Equatable, Codable, Sendable {
7198
/// Defines hints as to how clients might communicate with a particular authenticator in order to obtain an
7299
/// assertion for a specific credential
73100
public struct AuthenticatorTransport: UnreferencedStringEnumeration, Sendable {
@@ -119,6 +146,14 @@ public struct PublicKeyCredentialDescriptor: Equatable, Encodable, Sendable {
119146
try container.encode(id.base64URLEncodedString(), forKey: .id)
120147
try container.encodeIfPresent(transports, forKey: .transports)
121148
}
149+
150+
public init(from decoder: any Decoder) throws {
151+
let container = try decoder.container(keyedBy: CodingKeys.self)
152+
153+
self.type = try container.decode(CredentialType.self, forKey: .type)
154+
self.id = try container.decodeBytesFromURLEncodedBase64(forKey: .id)
155+
self.transports = try container.decodeIfPresent([AuthenticatorTransport].self, forKey: .transports) ?? []
156+
}
122157

123158
private enum CodingKeys: String, CodingKey {
124159
case type

Sources/WebAuthn/Ceremonies/Registration/AuthenticatorAttestationResponse.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,21 @@ public struct AuthenticatorAttestationResponse: Sendable {
2929
public let attestationObject: [UInt8]
3030
}
3131

32-
extension AuthenticatorAttestationResponse: Decodable {
32+
extension AuthenticatorAttestationResponse: Codable {
3333
public init(from decoder: any Decoder) throws {
3434
let container = try decoder.container(keyedBy: CodingKeys.self)
3535

3636
clientDataJSON = try container.decodeBytesFromURLEncodedBase64(forKey: .clientDataJSON)
3737
attestationObject = try container.decodeBytesFromURLEncodedBase64(forKey: .attestationObject)
3838
}
39+
40+
public func encode(to encoder: any Encoder) throws {
41+
var container = encoder.container(keyedBy: CodingKeys.self)
42+
43+
try container.encode(clientDataJSON.base64URLEncodedString(), forKey: .clientDataJSON)
44+
try container.encode(attestationObject.base64URLEncodedString(), forKey: .attestationObject)
45+
}
46+
3947

4048
private enum CodingKeys: String, CodingKey {
4149
case clientDataJSON

Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import Foundation
1919
/// `Encodable` byte arrays are base64url encoded.
2020
///
2121
/// - SeeAlso: https://www.w3.org/TR/webauthn-2/#dictionary-makecredentialoptions
22-
public struct PublicKeyCredentialCreationOptions: Encodable, Sendable {
22+
public struct PublicKeyCredentialCreationOptions: Codable, Sendable {
2323
/// A byte array randomly generated by the Relying Party. Should be at least 16 bytes long to ensure sufficient
2424
/// entropy.
2525
///
@@ -28,24 +28,24 @@ public struct PublicKeyCredentialCreationOptions: Encodable, Sendable {
2828
public let challenge: [UInt8]
2929

3030
/// Contains names and an identifier for the user account performing the registration
31-
public let user: PublicKeyCredentialUserEntity
31+
public var user: PublicKeyCredentialUserEntity
3232

3333
/// Contains a name and an identifier for the Relying Party responsible for the request
34-
public let relyingParty: PublicKeyCredentialRelyingPartyEntity
34+
public var relyingParty: PublicKeyCredentialRelyingPartyEntity
3535

3636
/// A list of key types and signature algorithms the Relying Party supports. Ordered from most preferred to least
3737
/// preferred.
38-
public let publicKeyCredentialParameters: [PublicKeyCredentialParameters]
38+
public var publicKeyCredentialParameters: [PublicKeyCredentialParameters]
3939

4040
/// A time, in seconds, that the caller is willing to wait for the call to complete. This is treated as a
4141
/// hint, and may be overridden by the client.
4242
///
4343
/// - Note: When encoded, this value is represented in milleseconds as a ``UInt32``.
44-
public let timeout: Duration?
44+
public var timeout: Duration?
4545

4646
/// Sets the Relying Party's preference for attestation conveyance. At the time of writing only `none` is
4747
/// supported.
48-
public let attestation: AttestationConveyancePreference
48+
public var attestation: AttestationConveyancePreference
4949

5050
public func encode(to encoder: any Encoder) throws {
5151
var container = encoder.container(keyedBy: CodingKeys.self)
@@ -57,6 +57,35 @@ public struct PublicKeyCredentialCreationOptions: Encodable, Sendable {
5757
try container.encodeIfPresent(timeout?.milliseconds, forKey: .timeout)
5858
try container.encode(attestation, forKey: .attestation)
5959
}
60+
61+
public init(from decoder: any Decoder) throws {
62+
let values = try decoder.container(keyedBy: CodingKeys.self)
63+
64+
self.challenge = try values.decodeBytesFromURLEncodedBase64(forKey: .challenge)
65+
self.user = try values.decode(PublicKeyCredentialUserEntity.self, forKey: .user)
66+
self.relyingParty = try values.decode(PublicKeyCredentialRelyingPartyEntity.self, forKey: .relyingParty)
67+
self.publicKeyCredentialParameters = try values.decode([PublicKeyCredentialParameters].self, forKey: .publicKeyCredentialParameters)
68+
if let timeout = try values.decodeIfPresent(UInt32.self, forKey: .timeout) {
69+
self.timeout = .milliseconds(timeout)
70+
}
71+
self.attestation = try values.decode(AttestationConveyancePreference.self, forKey: .attestation)
72+
}
73+
74+
public init(
75+
challenge: [UInt8],
76+
user: PublicKeyCredentialUserEntity,
77+
relyingParty: PublicKeyCredentialRelyingPartyEntity,
78+
publicKeyCredentialParameters: [PublicKeyCredentialParameters],
79+
timeout: Duration?,
80+
attestation: AttestationConveyancePreference
81+
) {
82+
self.challenge = challenge
83+
self.user = user
84+
self.relyingParty = relyingParty
85+
self.publicKeyCredentialParameters = publicKeyCredentialParameters
86+
self.timeout = timeout
87+
self.attestation = attestation
88+
}
6089

6190
private enum CodingKeys: String, CodingKey {
6291
case challenge
@@ -70,7 +99,7 @@ public struct PublicKeyCredentialCreationOptions: Encodable, Sendable {
7099

71100
// MARK: - Credential parameters
72101
/// From §5.3 (https://w3c.github.io/TR/webauthn/#dictionary-credential-params)
73-
public struct PublicKeyCredentialParameters: Equatable, Encodable, Sendable {
102+
public struct PublicKeyCredentialParameters: Equatable, Codable, Sendable {
74103
/// The type of credential to be created. At the time of writing always ``CredentialType/publicKey``.
75104
public let type: CredentialType
76105
/// The cryptographic signature algorithm with which the newly generated credential will be used, and thus also
@@ -87,6 +116,13 @@ public struct PublicKeyCredentialParameters: Equatable, Encodable, Sendable {
87116
self.type = type
88117
self.alg = alg
89118
}
119+
120+
public init(from decoder: any Decoder) throws {
121+
let container = try decoder.container(keyedBy: CodingKeys.self)
122+
123+
self.type = try container.decode(CredentialType.self, forKey: .type)
124+
self.alg = try container.decode(COSEAlgorithmIdentifier.self, forKey: .alg)
125+
}
90126
}
91127

92128
extension Array where Element == PublicKeyCredentialParameters {
@@ -103,22 +139,26 @@ extension Array where Element == PublicKeyCredentialParameters {
103139
/// From §5.4.2 (https://www.w3.org/TR/webauthn/#sctn-rp-credential-params).
104140
/// The PublicKeyCredentialRelyingPartyEntity dictionary is used to supply additional Relying Party attributes when
105141
/// creating a new credential.
106-
public struct PublicKeyCredentialRelyingPartyEntity: Encodable, Sendable {
142+
public struct PublicKeyCredentialRelyingPartyEntity: Codable, Sendable {
107143
/// A unique identifier for the Relying Party entity.
108-
public let id: String
144+
public var id: String
109145

110146
/// A human-readable identifier for the Relying Party, intended only for display. For example, "ACME Corporation",
111147
/// "Wonderful Widgets, Inc." or "ОАО Примертех".
112-
public let name: String
148+
public var name: String
113149

150+
public init(id: String, name: String) {
151+
self.id = id
152+
self.name = name
153+
}
114154
}
115155

116156
/// From §5.4.3 (https://www.w3.org/TR/webauthn/#dictionary-user-credential-params)
117157
/// The PublicKeyCredentialUserEntity dictionary is used to supply additional user account attributes when
118158
/// creating a new credential.
119159
///
120160
/// When encoding using `Encodable`, `id` is base64url encoded.
121-
public struct PublicKeyCredentialUserEntity: Encodable, Sendable {
161+
public struct PublicKeyCredentialUserEntity: Codable, Sendable {
122162
/// Generated by the Relying Party, unique to the user account, and must not contain personally identifying
123163
/// information about the user.
124164
///
@@ -149,6 +189,15 @@ public struct PublicKeyCredentialUserEntity: Encodable, Sendable {
149189
try container.encode(name, forKey: .name)
150190
try container.encode(displayName, forKey: .displayName)
151191
}
192+
193+
public init(from decoder: any Decoder) throws {
194+
let container = try decoder.container(keyedBy: CodingKeys.self)
195+
196+
self.id = try container.decodeBytesFromURLEncodedBase64(forKey: .id)
197+
self.name = try container.decode(String.self, forKey: .name)
198+
self.displayName = try container.decode(String.self, forKey: .displayName)
199+
}
200+
152201

153202
private enum CodingKeys: String, CodingKey {
154203
case id

Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public struct RegistrationCredential: Sendable {
3131
public let attestationResponse: AuthenticatorAttestationResponse
3232
}
3333

34-
extension RegistrationCredential: Decodable {
34+
extension RegistrationCredential: Codable {
3535
public init(from decoder: any Decoder) throws {
3636
let container = try decoder.container(keyedBy: CodingKeys.self)
3737

@@ -47,6 +47,15 @@ extension RegistrationCredential: Decodable {
4747
self.rawID = rawID
4848
attestationResponse = try container.decode(AuthenticatorAttestationResponse.self, forKey: .attestationResponse)
4949
}
50+
51+
public func encode(to encoder: any Encoder) throws {
52+
var container = encoder.container(keyedBy: CodingKeys.self)
53+
54+
try container.encode(id, forKey: .id)
55+
try container.encode(rawID.base64URLEncodedString(), forKey: .rawID)
56+
try container.encode(attestationResponse, forKey: .attestationResponse)
57+
}
58+
5059

5160
private enum CodingKeys: String, CodingKey {
5261
case id

Sources/WebAuthn/Ceremonies/Shared/COSE/COSEAlgorithmIdentifier.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import Crypto
1717
/// COSEAlgorithmIdentifier From §5.10.5. A number identifying a cryptographic algorithm. The algorithm
1818
/// identifiers SHOULD be values registered in the IANA COSE Algorithms registry
1919
/// [https://www.w3.org/TR/webauthn/#biblio-iana-cose-algs-reg], for instance, -7 for "ES256" and -257 for "RS256".
20-
public enum COSEAlgorithmIdentifier: Int, RawRepresentable, CaseIterable, Encodable, Sendable {
20+
public enum COSEAlgorithmIdentifier: Int, RawRepresentable, CaseIterable, Codable, Sendable {
2121
/// AlgES256 ECDSA with SHA-256
2222
case algES256 = -7
2323
/// AlgES384 ECDSA with SHA-384

0 commit comments

Comments
 (0)