|
| 1 | +Server Certificate API |
| 2 | +=== |
| 3 | +# Background |
| 4 | + |
| 5 | +The WebView2 team has been asked for an API to intercept when WebView2 cannot verify a server's digital certificate while loading a web page. |
| 6 | +This API provides an option to trust the server's TLS certificate at the application level and render the page without prompting the user about the TLS error or can cancel the request. |
| 7 | + |
| 8 | +# Description |
| 9 | + |
| 10 | +We propose adding `ServerCertificateErrorDetected` API that allows you to verify TLS certificates with errors, and either continue the request |
| 11 | +to load the resource or cancel the request. |
| 12 | + |
| 13 | +When the event is raised, WebView2 will pass a `CoreWebView2ServerCertificateErrorDetectedEventArgs` , which lets you view the TLS certificate request Uri, error information, inspect the certificate metadata and several options for responding to the TLS request. |
| 14 | + |
| 15 | +* You can perform your own verification of the certificate and allow the request to proceed if you trust it. |
| 16 | +* You can choose to cancel the request. |
| 17 | +* You can choose to display the default TLS interstitial page to let user respond to the request for page navigations. For others TLS certificate is rejected and the request is cancelled. |
| 18 | + |
| 19 | +We also propose adding a `ClearServerCertificateErrorActions` API which clears cached `COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_ALWAYS_ALLOW` response for proceeding with TLS certificate errors. |
| 20 | + |
| 21 | +# Examples |
| 22 | + |
| 23 | +## Win32 C++ |
| 24 | +``` cpp |
| 25 | +// When WebView2 doesn't trust a TLS certificate but host app does, this example bypasses |
| 26 | +// the default TLS interstitial page using the ReceivingServerCertificateError event handler and |
| 27 | +// continues the navigation to a server. Otherwise, cancel the request. |
| 28 | +void SettingsComponent::ToggleCustomServerCertificateSupport() |
| 29 | +{ |
| 30 | + auto m_webview11 = m_webview.query<ICoreWebView2_11>(); |
| 31 | + if (m_webview11) |
| 32 | + { |
| 33 | + if (m_ServerCertificateErrorToken.value == 0) |
| 34 | + { |
| 35 | + CHECK_FAILURE(m_webview11->add_ServerCertificateErrorDetected( |
| 36 | + Callback<ICoreWebView2ServerCertificateErrorDetectedEventHandler>( |
| 37 | + [this]( |
| 38 | + ICoreWebView2* sender, |
| 39 | + ICoreWebView2ServerCertificateErrorDetectedEventArgs* args) { |
| 40 | + COREWEBVIEW2_WEB_ERROR_STATUS errorStatus; |
| 41 | + CHECK_FAILURE(args->get_ErrorStatus(&errorStatus)); |
| 42 | + |
| 43 | + wil::com_ptr<ICoreWebView2Certificate> certificate = nullptr; |
| 44 | + CHECK_FAILURE(args->get_ServerCertificate(&certificate)); |
| 45 | + |
| 46 | + // Continues the request to a server with a TLS certificate if the error status |
| 47 | + // is of type `COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID` |
| 48 | + // and trusted by the host app. |
| 49 | + if (errorStatus == COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID && |
| 50 | + ValidateServerCertificate(certificate.get())) |
| 51 | + { |
| 52 | + CHECK_FAILURE(args->put_Action( |
| 53 | + COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_ALWAYS_ALLOW)); |
| 54 | + } |
| 55 | + else |
| 56 | + { |
| 57 | + // Cancel the request for other TLS certificate error types or if untrusted by the host app. |
| 58 | + CHECK_FAILURE(args->put_Action( |
| 59 | + COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_CANCEL)); |
| 60 | + } |
| 61 | + return S_OK; |
| 62 | + }) |
| 63 | + .Get(), |
| 64 | + &m_ServerCertificateErrorToken)); |
| 65 | + } |
| 66 | + else |
| 67 | + { |
| 68 | + CHECK_FAILURE(m_webview11->remove_ServerCertificateErrorDetected( |
| 69 | + m_ServerCertificateErrorToken)); |
| 70 | + m_ServerCertificateErrorToken.value = 0; |
| 71 | + } |
| 72 | + } |
| 73 | + else |
| 74 | + { |
| 75 | + FeatureNotAvailable(); |
| 76 | + } |
| 77 | +} |
| 78 | + |
| 79 | +// Function to validate the server certificate for untrusted root or self-signed certificate. |
| 80 | +// You may also choose to defer server certificate validation. |
| 81 | +bool ValidateServerCertificate(ICoreWebView2Certificate* certificate) |
| 82 | +{ |
| 83 | + // You may want to validate certificates in different ways depending on your app and scenario. |
| 84 | + // One way might be the following: |
| 85 | + // First, get the list of host app trusted certificates and its thumbprint. |
| 86 | + |
| 87 | + // Then get the last chain element using `ICoreWebView2Certificate::get_PemEncodedIssuerCertificateChain` |
| 88 | + // that contains the raw data of the untrusted root CA/self-signed certificate. Get the untrusted |
| 89 | + // root CA/self signed certificate thumbprint from the raw certificate data and |
| 90 | + // validate the thumbprint against the host app trusted certificate list. |
| 91 | + |
| 92 | + // Finally, return true if it exists in the host app's certificate trusted list, or otherwise return false. |
| 93 | + return true; |
| 94 | +} |
| 95 | + |
| 96 | +// This example clears `AlwaysAllow` response that are added for proceeding with TLS certificate errors. |
| 97 | +if (m_webview11) |
| 98 | +{ |
| 99 | + CHECK_FAILURE(m_webview11->ClearServerCertificateErrorActions( |
| 100 | + Callback< |
| 101 | + ICoreWebView2ClearServerCertificateErrorActionsCompletedHandler>( |
| 102 | + [](HRESULT result) -> HRESULT { |
| 103 | + auto showDialog = [result] { |
| 104 | + MessageBox( |
| 105 | + nullptr, |
| 106 | + (result == S_OK) |
| 107 | + ? L"Clear server certificate error actions are succeeded." |
| 108 | + : L"Clear server certificate error actions are failed.", |
| 109 | + L"Clear server certificate error actions", |
| 110 | + MB_OK); |
| 111 | + }; |
| 112 | + m_appWindow->RunAsync([showDialog]() { showDialog(); }); |
| 113 | + return S_OK; |
| 114 | + }) |
| 115 | + .Get())); |
| 116 | +} |
| 117 | +``` |
| 118 | +## . NET/ WinRT |
| 119 | +```c# |
| 120 | +// When WebView2 doesn't trust a TLS certificate but host app does, this example bypasses |
| 121 | +// the default TLS interstitial page using the ReceivingServerCertificateError event handler and |
| 122 | +// continues the navigation to a server. Otherwise, cancel the request. |
| 123 | +private bool _isServerCertificateError = false; |
| 124 | +void ToggleCustomServerCertificateSupport() |
| 125 | +{ |
| 126 | + if (!_isServerCertificateError) |
| 127 | + { |
| 128 | + webView.CoreWebView2.ServerCertificateErrorDetected += WebView_ServerCertificateErrorDetected; |
| 129 | + } |
| 130 | + else |
| 131 | + { |
| 132 | + webView.CoreWebView2.ServerCertificateErrorDetected -= WebView_ServerCertificateErrorDetected; |
| 133 | + } |
| 134 | + _isServerCertificateError = !_isServerCertificateError; |
| 135 | +
|
| 136 | + MessageBox.Show(this, "Custom server certificate support has been" + |
| 137 | + (_isServerCertificateError ? "enabled" : "disabled"), |
| 138 | + "Custom server certificate support"); |
| 139 | +} |
| 140 | +
|
| 141 | +void WebView_ServerCertificateErrorDetected(object sender, CoreWebView2ServerCertificateErrorDetectedEventArgs e) |
| 142 | +{ |
| 143 | + CoreWebView2Certificate certificate = e.ServerCertificate; |
| 144 | +
|
| 145 | + // Continues the request to a server with a TLS certificate if the error status |
| 146 | + // is of type `COREWEBVIEW2_WEB_ERROR_STATUS_CERTIFICATE_IS_INVALID` |
| 147 | + // and trusted by the host app. |
| 148 | + if (e.ErrorStatus == CoreWebView2WebErrorStatus.CertificateIsInvalid && |
| 149 | + ValidateServerCertificate(certificate)) |
| 150 | + { |
| 151 | + e.Action = CoreWebView2ServerCertificateErrorAction.AlwaysAllow; |
| 152 | + } |
| 153 | + else |
| 154 | + { |
| 155 | + // Cancel the request for other TLS certificate error types or if untrusted by the host app. |
| 156 | + e.Action = CoreWebView2ServerCertificateErrorAction.Cancel; |
| 157 | + } |
| 158 | +} |
| 159 | +
|
| 160 | +// Function to validate the server certificate for untrusted root or self-signed certificate. |
| 161 | +// You may also choose to defer server certificate validation. |
| 162 | +bool ValidateServerCertificate(CoreWebView2Certificate certificate) |
| 163 | +{ |
| 164 | + // You may want to validate certificates in different ways depending on your app and scenario. |
| 165 | + // One way might be the following: |
| 166 | + // First, get the list of host app trusted certificates and its thumbprint. |
| 167 | +
|
| 168 | + // Then get the last chain element using `ICoreWebView2Certificate::get_PemEncodedIssuerCertificateChain` |
| 169 | + // that contains the raw data of the untrusted root CA/self-signed certificate. Get the untrusted |
| 170 | + // root CA/self signed certificate thumbprint from the raw certificate data and |
| 171 | + // validate the thumbprint against the host app trusted certificate list. |
| 172 | +
|
| 173 | + // Finally, return true if it exists in the host app's certificate trusted list, or otherwise return false. |
| 174 | + return true; |
| 175 | +} |
| 176 | +
|
| 177 | +// This example clears `AlwaysAllow` response that are added for proceeding with TLS certificate errors. |
| 178 | +async void ClearServerCertificateErrorActions() |
| 179 | +{ |
| 180 | + bool isSuccessful = await webView.CoreWebView2.ClearServerCertificateErrorActionsAsync(); |
| 181 | + MessageBox.Show(this, "message", "Clear server certificate error actions are succeeded"); |
| 182 | +} |
| 183 | +``` |
| 184 | + |
| 185 | +# API Details |
| 186 | + |
| 187 | +## Win32 C++ |
| 188 | +``` cpp |
| 189 | +[v1_enum] typedef enum COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION { |
| 190 | + /// Indicates to ignore the warning and continue the request with the TLS |
| 191 | + /// certificate. This decision is cached for the RequestUri's host and the server |
| 192 | + /// certificate in the session. |
| 193 | + COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_ALWAYS_ALLOW, |
| 194 | + |
| 195 | + /// Indicates to reject the certificate and cancel the request. |
| 196 | + COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_CANCEL, |
| 197 | + |
| 198 | + /// Indicates to display the default TLS interstitial page to user for page navigations. |
| 199 | + /// For others TLS certificate is rejected and the request is cancelled. |
| 200 | + COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_DEFAULT |
| 201 | +} COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION; |
| 202 | + |
| 203 | +[uuid(4B7FF0D2-8203-48B0-ACBF-ED9CFF82567A), object, pointer_default(unique)] |
| 204 | +interface ICoreWebView2_11 : ICoreWebView2_10 { |
| 205 | + /// Add an event handler for the ServerCertificateErrorDetected event. |
| 206 | + /// The ServerCertificateErrorDetected event is raised when the WebView2 |
| 207 | + /// cannot verify server's digital certificate while loading a web page. |
| 208 | + /// |
| 209 | + /// This event will raise for all web resources and follows the `WebResourceRequested` event. |
| 210 | + /// |
| 211 | + /// If you don't handle the event, WebView2 will show the default TLS interstitial page to user. |
| 212 | + /// |
| 213 | + /// WebView2 caches the response when action is `COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_ALWAYS_ALLOW` |
| 214 | + /// for the RequestUri's host and the server certificate in the session and the `ServerCertificateErrorDetected` |
| 215 | + /// event won't be raised again. |
| 216 | + /// |
| 217 | + /// To raise the event again you must clear the cache using `ClearServerCertificateErrorActions`. |
| 218 | + /// |
| 219 | + /// \snippet SettingsComponent.cpp ServerCertificateErrorDetected1 |
| 220 | + HRESULT add_ServerCertificateErrorDetected( |
| 221 | + [in] ICoreWebView2ServerCertificateErrorDetectedEventHandler* |
| 222 | + eventHandler, |
| 223 | + [out] EventRegistrationToken* token); |
| 224 | + /// Remove an event handler previously added with add_ServerCertificateErrorDetected. |
| 225 | + HRESULT remove_ServerCertificateErrorDetected([in] EventRegistrationToken token); |
| 226 | + |
| 227 | + /// Clears all cached decisions to proceed with TLS certificate errors from the |
| 228 | + /// ServerCertificateErrorDetected event for all WebView2's sharing the same session. |
| 229 | + HRESULT ClearServerCertificateErrorActions( |
| 230 | + [in] ICoreWebView2ClearServerCertificateErrorActionsCompletedHandler* |
| 231 | + handler); |
| 232 | +} |
| 233 | + |
| 234 | +/// Receives the result of the `ClearServerCertificateErrorActions` method. |
| 235 | +[uuid(2F7B173D-3CE1-4945-BDE6-94F4C57B7209), object, pointer_default(unique)] |
| 236 | +interface ICoreWebView2ClearServerCertificateErrorActionsCompletedHandler : IUnknown { |
| 237 | + /// Provides the result of the corresponding asynchronous method. |
| 238 | + HRESULT Invoke([in] HRESULT errorCode); |
| 239 | +} |
| 240 | + |
| 241 | +/// An event handler for the `ServerCertificateErrorDetected` event. |
| 242 | +[uuid(AAC28793-11FC-4EE5-A8D4-25A0279B1551), object, pointer_default(unique)] |
| 243 | +interface ICoreWebView2ServerCertificateErrorDetectedEventHandler : IUnknown { |
| 244 | + /// Provides the event args for the corresponding event. |
| 245 | + HRESULT Invoke([in] ICoreWebView2* sender, |
| 246 | + [in] ICoreWebView2ServerCertificateErrorDetectedEventArgs* |
| 247 | + args); |
| 248 | +} |
| 249 | + |
| 250 | +/// Event args for the `ServerCertificateErrorDetected` event. |
| 251 | +[uuid(24EADEE7-31F9-447F-9FE7-7C13DC738C32), object, pointer_default(unique)] |
| 252 | +interface ICoreWebView2ServerCertificateErrorDetectedEventArgs : IUnknown { |
| 253 | + /// The TLS error code for the invalid certificate. |
| 254 | + [propget] HRESULT ErrorStatus([out, retval] COREWEBVIEW2_WEB_ERROR_STATUS* value); |
| 255 | + |
| 256 | + /// URI associated with the request for the invalid certificate. |
| 257 | + [propget] HRESULT RequestUri([out, retval] LPWSTR* value); |
| 258 | + |
| 259 | + /// Returns the server certificate. |
| 260 | + [propget] HRESULT ServerCertificate([out, retval] ICoreWebView2Certificate** value); |
| 261 | + |
| 262 | + /// The action of the server certificate error detection. |
| 263 | + /// The default value is `COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION_DEFAULT`. |
| 264 | + [propget] HRESULT Action([out, retval] COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION* value); |
| 265 | + |
| 266 | + /// Sets the `Action` property. |
| 267 | + [propput] HRESULT Action([in] COREWEBVIEW2_SERVER_CERTIFICATE_ERROR_ACTION value); |
| 268 | + |
| 269 | + /// Returns an `ICoreWebView2Deferral` object. Use this operation to |
| 270 | + /// complete the event at a later time. |
| 271 | + HRESULT GetDeferral([out, retval] ICoreWebView2Deferral** deferral); |
| 272 | +} |
| 273 | +``` |
| 274 | + |
| 275 | +```c# (but really MIDL3) |
| 276 | +namespace Microsoft.Web.WebView2.Core |
| 277 | +{ |
| 278 | + enum CoreWebView2ServerCertificateErrorAction |
| 279 | + { |
| 280 | + AlwaysAllow = 0, |
| 281 | + Cancel = 1, |
| 282 | + Default = 2 |
| 283 | + }; |
| 284 | + |
| 285 | + runtimeclass CoreWebView2ServerCertificateErrorDetectedEventArgs |
| 286 | + { |
| 287 | + CoreWebView2WebErrorStatus ErrorStatus { get; }; |
| 288 | + String RequestUri { get; }; |
| 289 | + CoreWebView2Certificate ServerCertificate { get; }; |
| 290 | + CoreWebView2ServerCertificateErrorAction Action { get; set; }; |
| 291 | + Windows.Foundation.Deferral GetDeferral(); |
| 292 | + } |
| 293 | + |
| 294 | + runtimeclass CoreWebView2 |
| 295 | + { |
| 296 | + // ... |
| 297 | + [interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2_11")] |
| 298 | + { |
| 299 | + event Windows.Foundation.TypedEventHandler<CoreWebView2, CoreWebView2ServerCertificateErrorDetectedEventArgs> ServerCertificateErrorDetected; |
| 300 | + Windows.Foundation.IAsyncAction ClearServerCertificateErrorActionsAsync(); |
| 301 | + } |
| 302 | + } |
| 303 | +} |
| 304 | +``` |
0 commit comments