6
6
// Copyright © 2024 Mochi Development, Inc. All rights reserved.
7
7
//
8
8
9
+ @preconcurrency import Crypto
9
10
import Foundation
10
11
11
12
/// Represents a subscriber registration from the browser.
@@ -31,37 +32,89 @@ public protocol SubscriberProtocol: Sendable {
31
32
/// The set of cryptographic secrets shared by the browser (is. user agent) along with a subscription.
32
33
///
33
34
/// - 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
42
38
43
39
/// The public key a shared secret can be derived from for message encryption.
44
40
///
45
41
/// - 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
47
43
48
44
/// The authentication secret to validate our ability to send a subscriber push messages.
49
45
///
50
46
/// - 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
52
48
53
49
/// Initialize key material with a public key and authentication secret from a user agent.
54
50
///
55
51
/// - Parameters:
56
52
/// - publicKey: The public key a shared secret can be derived from for message encryption.
57
53
/// - authenticationSecret: The authentication secret to validate our ability to send a subscriber push messages.
58
54
public init (
59
- publicKey: String ,
60
- authenticationSecret: String
55
+ publicKey: P256 . Signing . PublicKey ,
56
+ authenticationSecret: Salt
61
57
) {
62
58
self . publicKey = publicKey
63
59
self . authenticationSecret = authenticationSecret
64
60
}
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
+ 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
+ }
65
118
}
66
119
67
120
/// A default subscriber implementation that can be used to decode subscriptions encoded by client-side JavaScript directly.
0 commit comments