Skip to content

Commit a634d96

Browse files
Updated UserAgentKeyMaterial to use fully qualified types when decoded
1 parent f8ea193 commit a634d96

File tree

1 file changed

+66
-13
lines changed

1 file changed

+66
-13
lines changed

Sources/WebPush/Subscriber.swift

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// Copyright © 2024 Mochi Development, Inc. All rights reserved.
77
//
88

9+
@preconcurrency import Crypto
910
import Foundation
1011

1112
/// Represents a subscriber registration from the browser.
@@ -31,37 +32,89 @@ public protocol SubscriberProtocol: Sendable {
3132
/// The set of cryptographic secrets shared by the browser (is. user agent) along with a subscription.
3233
///
3334
/// - SeeAlso: [RFC8291 Message Encryption for Web Push §2.1. Key and Secret Distribution](https://datatracker.ietf.org/doc/html/rfc8291#section-2.1)
34-
public struct UserAgentKeyMaterial: Codable, Hashable, Sendable {
35-
/// The encoded representation of a subscriber's key material.
36-
///
37-
/// - SeeAlso: [Push API Working Draft §8.1. `PushEncryptionKeyName` enumeration](https://www.w3.org/TR/push-api/#pushencryptionkeyname-enumeration)
38-
enum CodingKeys: String, CodingKey {
39-
case publicKey = "p256dh"
40-
case authenticationSecret = "auth"
41-
}
35+
public struct UserAgentKeyMaterial: Sendable {
36+
/// The underlying type of an authentication secret.
37+
public typealias Salt = Data
4238

4339
/// The public key a shared secret can be derived from for message encryption.
4440
///
4541
/// - SeeAlso: [Push API Working Draft §8.1. `PushEncryptionKeyName` enumeration — `p256dh`](https://www.w3.org/TR/push-api/#dom-pushencryptionkeyname-p256dh)
46-
public var publicKey: String
42+
public var publicKey: P256.Signing.PublicKey
4743

4844
/// The authentication secret to validate our ability to send a subscriber push messages.
4945
///
5046
/// - SeeAlso: [Push API Working Draft §8.1. `PushEncryptionKeyName` enumeration — `auth`](https://www.w3.org/TR/push-api/#dom-pushencryptionkeyname-auth)
51-
public var authenticationSecret: String
47+
public var authenticationSecret: Salt
5248

5349
/// Initialize key material with a public key and authentication secret from a user agent.
5450
///
5551
/// - Parameters:
5652
/// - publicKey: The public key a shared secret can be derived from for message encryption.
5753
/// - authenticationSecret: The authentication secret to validate our ability to send a subscriber push messages.
5854
public init(
59-
publicKey: String,
60-
authenticationSecret: String
55+
publicKey: P256.Signing.PublicKey,
56+
authenticationSecret: Salt
6157
) {
6258
self.publicKey = publicKey
6359
self.authenticationSecret = authenticationSecret
6460
}
61+
62+
/// Initialize key material with a public key and authentication secret from a user agent.
63+
///
64+
/// - Parameters:
65+
/// - publicKey: The public key a shared secret can be derived from for message encryption.
66+
/// - authenticationSecret: The authentication secret to validate our ability to send a subscriber push messages.
67+
public init(
68+
publicKey: String,
69+
authenticationSecret: String
70+
) throws {
71+
guard let publicKeyData = Data(base64URLEncoded: publicKey)
72+
else { throw CancellationError() } // invalid public key error (underlying error = URLDecoding error)
73+
do {
74+
self.publicKey = try P256.Signing.PublicKey(x963Representation: publicKeyData)
75+
} catch { throw CancellationError() } // invalid public key error (underlying error = error)
76+
77+
guard let authenticationSecretData = Data(base64URLEncoded: authenticationSecret)
78+
else { throw CancellationError() } // invalid authentication secret error (underlying error = URLDecoding error)
79+
80+
self.authenticationSecret = authenticationSecretData
81+
}
82+
}
83+
84+
extension UserAgentKeyMaterial: Hashable {
85+
public static func == (lhs: UserAgentKeyMaterial, rhs: UserAgentKeyMaterial) -> Bool {
86+
lhs.publicKey.x963Representation == rhs.publicKey.x963Representation
87+
&& lhs.authenticationSecret == rhs.authenticationSecret
88+
}
89+
90+
public func hash(into hasher: inout Hasher) {
91+
hasher.combine(publicKey.x963Representation)
92+
hasher.combine(authenticationSecret)
93+
}
94+
}
95+
96+
extension UserAgentKeyMaterial: Codable {
97+
/// The encoded representation of a subscriber's key material.
98+
///
99+
/// - SeeAlso: [Push API Working Draft §8.1. `PushEncryptionKeyName` enumeration](https://www.w3.org/TR/push-api/#pushencryptionkeyname-enumeration)
100+
public enum CodingKeys: String, CodingKey {
101+
case publicKey = "p256dh"
102+
case authenticationSecret = "auth"
103+
}
104+
105+
public init(from decoder: Decoder) throws {
106+
let container = try decoder.container(keyedBy: CodingKeys.self)
107+
108+
let publicKeyString = try container.decode(String.self, forKey: .publicKey)
109+
let authenticationSecretString = try container.decode(String.self, forKey: .authenticationSecret)
110+
try self.init(publicKey: publicKeyString, authenticationSecret: authenticationSecretString)
111+
}
112+
113+
public func encode(to encoder: any Encoder) throws {
114+
var container = encoder.container(keyedBy: CodingKeys.self)
115+
try container.encode(publicKey.x963Representation.base64URLEncodedString(), forKey: .publicKey)
116+
try container.encode(authenticationSecret.base64URLEncodedString(), forKey: .authenticationSecret)
117+
}
65118
}
66119

67120
/// A default subscriber implementation that can be used to decode subscriptions encoded by client-side JavaScript directly.
@@ -83,7 +136,7 @@ public struct Subscriber: SubscriberProtocol, Codable, Hashable, Sendable {
83136
///
84137
/// - Note: The VAPID Key ID must be manually added to the structure supplied by the spec.
85138
/// - SeeAlso: [Push API Working Draft §8. `PushSubscription` interface](https://www.w3.org/TR/push-api/#pushsubscription-interface).
86-
enum CodingKeys: String, CodingKey {
139+
public enum CodingKeys: String, CodingKey {
87140
case endpoint = "endpoint"
88141
case userAgentKeyMaterial = "keys"
89142
case vapidKeyID = "applicationServerKey"

0 commit comments

Comments
 (0)