Skip to content

Commit 1d4afc6

Browse files
authored
Allow the peer's validated certificate chain to be returned in the custom verification handler (#553)
Motivation: Users are currently able to register a custom callback in `NIOSSLClientHandler` and `NIOSSLServerHandler` for verifiying the certificates presented by the peer. However, there is no mechanism for storing additional metadata for future use. This change adds support for the peer's validated certificate chain to be returned from the custom verification callback and later be accessed from the handler. Modifications: - Added a new custom verification callback type `NIOSSLCustomVerificationCallbackWithMetadata`. - This type is identical to the existing `NIOSSLCustomVerificationCallback`, with the exception that callers must complete the callback with a `NIOSSLVerificationResultWithMetadata` (also introduced in this change). This result type can either be initialized with no fields, or with a validated certificate chain. - Added properties/methods to `NIOSSLHandler`, `Channel`, and `ChannelPipeline` for accessing the validated certificate chain. Result: Users are now able to store the peer's validated certificate chain from the custom verification callback and use the result downstream.
1 parent 191e95e commit 1d4afc6

File tree

6 files changed

+585
-70
lines changed

6 files changed

+585
-70
lines changed

Sources/NIOSSL/NIOSSLClientHandler.swift

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public final class NIOSSLClientHandler: NIOSSLHandler {
6767
try self.init(
6868
context: context,
6969
serverHostname: serverHostname,
70-
optionalCustomVerificationCallback: nil,
70+
optionalCustomVerificationCallbackManager: nil,
7171
optionalAdditionalPeerCertificateVerificationCallback: nil
7272
)
7373
}
@@ -119,6 +119,10 @@ public final class NIOSSLClientHandler: NIOSSLHandler {
119119
///
120120
/// If set, this callback is provided the certificates presented by the peer. NIOSSL will not have pre-processed them. The callback will not be used if the
121121
/// ``TLSConfiguration`` that was used to construct the ``NIOSSLContext`` has ``TLSConfiguration/certificateVerification`` set to ``CertificateVerification/none``.
122+
///
123+
/// - Note: Use ``init(context:serverHostname:customVerificationCallbackWithMetadata:)`` to provide a custom
124+
/// verification callback where the peer's *validated* certificate chain can be returned. This data can then be
125+
/// accessed from the handler.
122126
public convenience init(
123127
context: NIOSSLContext,
124128
serverHostname: String?,
@@ -127,7 +131,39 @@ public final class NIOSSLClientHandler: NIOSSLHandler {
127131
try self.init(
128132
context: context,
129133
serverHostname: serverHostname,
130-
optionalCustomVerificationCallback: customVerificationCallback,
134+
optionalCustomVerificationCallbackManager: CustomVerifyManager(callback: customVerificationCallback),
135+
optionalAdditionalPeerCertificateVerificationCallback: nil
136+
)
137+
}
138+
139+
/// Construct a new ``NIOSSLClientHandler`` with the given `context` and a specific `serverHostname`.
140+
///
141+
/// - parameters:
142+
/// - context: The ``NIOSSLContext`` to use on this connection.
143+
/// - serverHostname: The hostname of the server we're trying to connect to, if known. This will be used in the SNI extension,
144+
/// and used to validate the server certificate.
145+
/// - customVerificationCallbackWithMetadata: A callback to use that will override NIOSSL's normal verification
146+
/// logic. If validation is successful, the peer's validated certificate chain can be returned, and later
147+
/// accessed via ``NIOSSLHandler/peerValidatedCertificateChain``. The callback will not be used if the
148+
/// ``TLSConfiguration`` that was used to construct the ``NIOSSLContext`` has
149+
/// ``TLSConfiguration/certificateVerification`` set to ``CertificateVerification/none``.
150+
///
151+
/// - This callback is provided the certificates presented by the peer. NIOSSL will not have pre-processed
152+
/// them. Therefore, a validated chain must be derived *within* this callback (potentially involving fetching
153+
/// additional intermediate certificates). The *validated* certificate chain returned in the promise result
154+
/// **must** be a verified path to a trusted root. Importantly, the certificates presented by the peer should
155+
/// not be assumed to be valid.
156+
public convenience init(
157+
context: NIOSSLContext,
158+
serverHostname: String?,
159+
customVerificationCallbackWithMetadata: @escaping NIOSSLCustomVerificationCallbackWithMetadata
160+
) throws {
161+
try self.init(
162+
context: context,
163+
serverHostname: serverHostname,
164+
optionalCustomVerificationCallbackManager: CustomVerifyManager(
165+
callback: customVerificationCallbackWithMetadata
166+
),
131167
optionalAdditionalPeerCertificateVerificationCallback: nil
132168
)
133169
}
@@ -143,6 +179,10 @@ public final class NIOSSLClientHandler: NIOSSLHandler {
143179
/// If set, this callback is provided the certificates presented by the peer. NIOSSL will not have pre-processed them. The callback will not be used if the
144180
/// ``TLSConfiguration`` that was used to construct the ``NIOSSLContext`` has ``TLSConfiguration/certificateVerification`` set to ``CertificateVerification/none``.
145181
/// - configuration: Configuration for this handler.
182+
///
183+
/// - Note: Use ``init(context:serverHostname:configuration:customVerificationCallbackWithMetadata:)`` to provide a
184+
/// custom verification callback where the peer's *validated* certificate chain can be returned. This data can
185+
/// then be accessed from the handler.
146186
public convenience init(
147187
context: NIOSSLContext,
148188
serverHostname: String?,
@@ -152,7 +192,42 @@ public final class NIOSSLClientHandler: NIOSSLHandler {
152192
try self.init(
153193
context: context,
154194
serverHostname: serverHostname,
155-
optionalCustomVerificationCallback: customVerificationCallback,
195+
optionalCustomVerificationCallbackManager: customVerificationCallback.map(CustomVerifyManager.init),
196+
optionalAdditionalPeerCertificateVerificationCallback: nil,
197+
configuration: configuration
198+
)
199+
}
200+
201+
/// Construct a new ``NIOSSLClientHandler`` with the given `context` and a specific `serverHostname`.
202+
///
203+
/// - parameters:
204+
/// - context: The ``NIOSSLContext`` to use on this connection.
205+
/// - serverHostname: The hostname of the server we're trying to connect to, if known. This will be used in the SNI extension,
206+
/// and used to validate the server certificate.
207+
/// - configuration: Configuration for this handler.
208+
/// - customVerificationCallbackWithMetadata: A callback to use that will override NIOSSL's normal verification
209+
/// logic. If validation is successful, the peer's validated certificate chain can be returned, and later
210+
/// accessed via ``NIOSSLHandler/peerValidatedCertificateChain``. The callback will not be used if the
211+
/// ``TLSConfiguration`` that was used to construct the ``NIOSSLContext`` has
212+
/// ``TLSConfiguration/certificateVerification`` set to ``CertificateVerification/none``.
213+
///
214+
/// - This callback is provided the certificates presented by the peer. NIOSSL will not have pre-processed
215+
/// them. Therefore, a validated chain must be derived *within* this callback (potentially involving fetching
216+
/// additional intermediate certificates). The *validated* certificate chain returned in the promise result
217+
/// **must** be a verified path to a trusted root. Importantly, the certificates presented by the peer should
218+
/// not be assumed to be valid.
219+
public convenience init(
220+
context: NIOSSLContext,
221+
serverHostname: String?,
222+
configuration: Configuration,
223+
customVerificationCallbackWithMetadata: @escaping NIOSSLCustomVerificationCallbackWithMetadata
224+
) throws {
225+
try self.init(
226+
context: context,
227+
serverHostname: serverHostname,
228+
optionalCustomVerificationCallbackManager: CustomVerifyManager(
229+
callback: customVerificationCallbackWithMetadata
230+
),
156231
optionalAdditionalPeerCertificateVerificationCallback: nil,
157232
configuration: configuration
158233
)
@@ -167,7 +242,7 @@ public final class NIOSSLClientHandler: NIOSSLHandler {
167242
try .init(
168243
context: context,
169244
serverHostname: serverHostname,
170-
optionalCustomVerificationCallback: nil,
245+
optionalCustomVerificationCallbackManager: nil,
171246
optionalAdditionalPeerCertificateVerificationCallback: additionalPeerCertificateVerificationCallback
172247
)
173248
}
@@ -176,7 +251,7 @@ public final class NIOSSLClientHandler: NIOSSLHandler {
176251
internal init(
177252
context: NIOSSLContext,
178253
serverHostname: String?,
179-
optionalCustomVerificationCallback: NIOSSLCustomVerificationCallback?,
254+
optionalCustomVerificationCallbackManager: CustomVerifyManager?,
180255
optionalAdditionalPeerCertificateVerificationCallback: _NIOAdditionalPeerCertificateVerificationCallback?,
181256
maxWriteSize: Int = defaultMaxWriteSize,
182257
configuration: Configuration = .init()
@@ -199,8 +274,8 @@ public final class NIOSSLClientHandler: NIOSSLHandler {
199274
}
200275
}
201276

202-
if let verificationCallback = optionalCustomVerificationCallback {
203-
connection.setCustomVerificationCallback(CustomVerifyManager(callback: verificationCallback))
277+
if let verificationCallbackManager = optionalCustomVerificationCallbackManager {
278+
connection.setCustomVerificationCallback(verificationCallbackManager)
204279
}
205280

206281
super.init(

Sources/NIOSSL/NIOSSLHandler.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,26 @@ extension NIOSSLHandler {
817817
public var peerCertificate: NIOSSLCertificate? {
818818
self.connection.getPeerCertificate()
819819
}
820+
821+
/// Return the *validated* certificate chain from the verified peer after handshake has completed.
822+
///
823+
/// This property will only contain a value if the handler was initialized with a custom certificate verification
824+
/// callback (``NIOSSLCustomVerificationCallbackWithMetadata``) *and* if the promise in the callback was
825+
/// successfully completed with ``NIOSSLVerificationResultWithMetadata/certificateVerified(_:)`` (containing a
826+
/// ``VerificationMetadata`` instance with a ``ValidatedCertificateChain``). If either of these conditions are not
827+
/// met, this property will be `nil`.
828+
///
829+
/// To create a `NIOSSLClientHandler` handler with a custom verification callback that can return the certificate
830+
/// chain, use:
831+
/// - ``NIOSSLClientHandler/init(context:serverHostname:customVerificationCallbackWithMetadata:)`` or
832+
/// - ``NIOSSLClientHandler/init(context:serverHostname:configuration:customVerificationCallbackWithMetadata:)``
833+
/// For `NIOSSLServerHandler`, use:
834+
/// - ``NIOSSLServerHandler/init(context:customVerificationCallbackWithMetadata:)`` or
835+
/// - ``NIOSSLServerHandler/init(context:configuration:customVerificationCallbackWithMetadata:)``
836+
///
837+
public var peerValidatedCertificateChain: ValidatedCertificateChain? {
838+
self.connection.customVerificationManager?.verificationMetadata?.validatedCertificateChain
839+
}
820840
}
821841

822842
extension Channel {
@@ -834,6 +854,12 @@ extension Channel {
834854
}
835855
}
836856

857+
/// API to retrieve the *validated* certificate chain of the peer. See ``NIOSSLHandler/peerValidatedCertificateChain``.
858+
public func nioSSL_peerValidatedCertificateChain() -> EventLoopFuture<ValidatedCertificateChain?> {
859+
self.pipeline.handler(type: NIOSSLHandler.self).map {
860+
$0.peerValidatedCertificateChain
861+
}
862+
}
837863
}
838864

839865
extension ChannelPipeline.SynchronousOperations {
@@ -848,6 +874,12 @@ extension ChannelPipeline.SynchronousOperations {
848874
let handler = try self.handler(type: NIOSSLHandler.self)
849875
return handler.peerCertificate
850876
}
877+
878+
/// API to retrieve the *validated* certificate chain of the peer. See ``NIOSSLHandler/peerValidatedCertificateChain``.
879+
public func nioSSL_peerValidatedCertificateChain() throws -> ValidatedCertificateChain? {
880+
let handler = try self.handler(type: NIOSSLHandler.self)
881+
return handler.peerValidatedCertificateChain
882+
}
851883
}
852884

853885
// MARK:- Extension APIs for users.

Sources/NIOSSL/NIOSSLServerHandler.swift

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public final class NIOSSLServerHandler: NIOSSLHandler {
2525
public convenience init(context: NIOSSLContext) {
2626
self.init(
2727
context: context,
28-
optionalCustomVerificationCallback: nil,
28+
optionalCustomVerificationCallbackManager: nil,
2929
optionalAdditionalPeerCertificateVerificationCallback: nil
3030
)
3131
}
@@ -59,13 +59,45 @@ public final class NIOSSLServerHandler: NIOSSLHandler {
5959
///
6060
/// If set, this callback is provided the certificates presented by the peer. NIOSSL will not have pre-processed them. The callback will not be used if the
6161
/// ``TLSConfiguration`` that was used to construct the ``NIOSSLContext`` has ``TLSConfiguration/certificateVerification`` set to ``CertificateVerification/none``.
62+
///
63+
/// - Note: Use ``init(context:customVerificationCallbackWithMetadata:)`` to provide a custom verification
64+
/// callback where the peer's *validated* certificate chain can be returned. This data can then be accessed from
65+
/// the handler.
6266
public convenience init(
6367
context: NIOSSLContext,
6468
customVerificationCallback: @escaping NIOSSLCustomVerificationCallback
6569
) {
6670
self.init(
6771
context: context,
68-
optionalCustomVerificationCallback: customVerificationCallback,
72+
optionalCustomVerificationCallbackManager: CustomVerifyManager(callback: customVerificationCallback),
73+
optionalAdditionalPeerCertificateVerificationCallback: nil
74+
)
75+
}
76+
77+
/// Construct a new ``NIOSSLServerHandler`` with the given `context` and a specific `serverHostname`.
78+
///
79+
/// - parameters:
80+
/// - context: The ``NIOSSLContext`` to use on this connection.
81+
/// - customVerificationCallbackWithMetadata: A callback to use that will override NIOSSL's normal verification
82+
/// logic. If validation is successful, the peer's validated certificate chain can be returned, and later
83+
/// accessed via ``NIOSSLHandler/peerValidatedCertificateChain``. The callback will not be used if the
84+
/// ``TLSConfiguration`` that was used to construct the ``NIOSSLContext`` has
85+
/// ``TLSConfiguration/certificateVerification`` set to ``CertificateVerification/none``.
86+
///
87+
/// - This callback is provided the certificates presented by the peer. NIOSSL will not have pre-processed
88+
/// them. Therefore, a validated chain must be derived *within* this callback (potentially involving fetching
89+
/// additional intermediate certificates). The *validated* certificate chain returned in the promise result
90+
/// **must** be a verified path to a trusted root. Importantly, the certificates presented by the peer should
91+
/// not be assumed to be valid.
92+
public convenience init(
93+
context: NIOSSLContext,
94+
customVerificationCallbackWithMetadata: @escaping NIOSSLCustomVerificationCallbackWithMetadata
95+
) {
96+
self.init(
97+
context: context,
98+
optionalCustomVerificationCallbackManager: CustomVerifyManager(
99+
callback: customVerificationCallbackWithMetadata
100+
),
69101
optionalAdditionalPeerCertificateVerificationCallback: nil
70102
)
71103
}
@@ -79,14 +111,49 @@ public final class NIOSSLServerHandler: NIOSSLHandler {
79111
/// If set, this callback is provided the certificates presented by the peer. NIOSSL will not have pre-processed them. The callback will not be used if the
80112
/// ``TLSConfiguration`` that was used to construct the ``NIOSSLContext`` has ``TLSConfiguration/certificateVerification`` set to ``CertificateVerification/none``.
81113
/// - configuration: Configuration for this handler.
114+
///
115+
/// - Note: Use ``init(context:configuration:customVerificationCallbackWithMetadata:)`` to provide a custom
116+
/// verification callback where the peer's *validated* certificate chain can be returned. This data can then be
117+
/// accessed from the handler.
82118
public convenience init(
83119
context: NIOSSLContext,
84120
customVerificationCallback: NIOSSLCustomVerificationCallback? = nil,
85121
configuration: Configuration
86122
) {
87123
self.init(
88124
context: context,
89-
optionalCustomVerificationCallback: customVerificationCallback,
125+
optionalCustomVerificationCallbackManager: customVerificationCallback.map(CustomVerifyManager.init),
126+
optionalAdditionalPeerCertificateVerificationCallback: nil,
127+
configuration: configuration
128+
)
129+
}
130+
131+
/// Construct a new ``NIOSSLServerHandler`` with the given `context` and a specific `serverHostname`.
132+
///
133+
/// - parameters:
134+
/// - context: The ``NIOSSLContext`` to use on this connection.
135+
/// - configuration: Configuration for this handler.
136+
/// - customVerificationCallbackWithMetadata: A callback to use that will override NIOSSL's normal verification
137+
/// logic. If validation is successful, the peer's validated certificate chain can be returned, and later
138+
/// accessed via ``NIOSSLHandler/peerValidatedCertificateChain``. The callback will not be used if the
139+
/// ``TLSConfiguration`` that was used to construct the ``NIOSSLContext`` has
140+
/// ``TLSConfiguration/certificateVerification`` set to ``CertificateVerification/none``.
141+
///
142+
/// - This callback is provided the certificates presented by the peer. NIOSSL will not have pre-processed
143+
/// them. Therefore, a validated chain must be derived *within* this callback (potentially involving fetching
144+
/// additional intermediate certificates). The *validated* certificate chain returned in the promise result
145+
/// **must** be a verified path to a trusted root. Importantly, the certificates presented by the peer should
146+
/// not be assumed to be valid.
147+
public convenience init(
148+
context: NIOSSLContext,
149+
configuration: Configuration,
150+
customVerificationCallbackWithMetadata: @escaping NIOSSLCustomVerificationCallbackWithMetadata
151+
) {
152+
self.init(
153+
context: context,
154+
optionalCustomVerificationCallbackManager: CustomVerifyManager(
155+
callback: customVerificationCallbackWithMetadata
156+
),
90157
optionalAdditionalPeerCertificateVerificationCallback: nil,
91158
configuration: configuration
92159
)
@@ -99,15 +166,15 @@ public final class NIOSSLServerHandler: NIOSSLHandler {
99166
) -> Self {
100167
.init(
101168
context: context,
102-
optionalCustomVerificationCallback: nil,
169+
optionalCustomVerificationCallbackManager: nil,
103170
optionalAdditionalPeerCertificateVerificationCallback: additionalPeerCertificateVerificationCallback
104171
)
105172
}
106173

107174
/// This exists to handle the explosion of initializers I got when I deprecated the first one.
108175
private init(
109176
context: NIOSSLContext,
110-
optionalCustomVerificationCallback: NIOSSLCustomVerificationCallback?,
177+
optionalCustomVerificationCallbackManager: CustomVerifyManager?,
111178
optionalAdditionalPeerCertificateVerificationCallback: _NIOAdditionalPeerCertificateVerificationCallback?,
112179
configuration: Configuration = .init()
113180
) {
@@ -117,8 +184,8 @@ public final class NIOSSLServerHandler: NIOSSLHandler {
117184

118185
connection.setAcceptState()
119186

120-
if let customVerificationCallback = optionalCustomVerificationCallback {
121-
connection.setCustomVerificationCallback(.init(callback: customVerificationCallback))
187+
if let customVerificationCallbackManager = optionalCustomVerificationCallbackManager {
188+
connection.setCustomVerificationCallback(customVerificationCallbackManager)
122189
}
123190

124191
super.init(

0 commit comments

Comments
 (0)