This is a first part of my small series of blogposts about unlocking hidden features of Windows 10 UWP WebView.
If you want to display web page content inside your Windows 10 UWP app, you need to use the WebView class. In standard scenarios you just set the Source property or use the Navigate(Uri) method to the target page and if your Internet connection works, the page shows up just like in Microsoft Edge.
But there is a caveat when using the method. If the target page is available only on HTTPS protocol and the Domain certificate for the target web is not valid (expired, self-signed, invalid domain name…) then the page won’t load, you only get the OnNavigationFailed event with WebErrorStatus.CertificateIsInvalid or similar indicator.
As you probably know, in standard browsers like IE, Chrome, Firefox or Edge there is always a semi-hidden option to override this certificate error and continue navigation to that page anyway, usually with a big warning that the connection won’t be secure and possible attacker could be eavesdropping your connection or even modifying the transmitted data. Yes, that’s the risk here, even though in 99.99% of cases no-one is actually eavesdropping you, only the server is just not properly configured.
If the target site uses self-signed HTTPS certificate and this self-signed certificate is otherwise valid for the target domain, there is a solution how to make the WebView work in this case – you just need to add the specific certificate inside Package.appxmanifest -> Declarations -> Certificates whitelist. Here you select the .cer file for your site with “TrustedPeople” as the Store name and all should just work. Another solution is to whitelist the Root certificate for this custom domain certificate and trust this self-signed Root. Unfortunately this solution only works for specific sites that you whitelist, not for any site with self-signed certificate.
In some valid scenarios you might need to display web content of HTTPS page with invalid certificate inside your Windows Store app, but how to do it? There is no property or method in WebView to override this behavior and enable navigation to a page with invalid certificate, but luckily I found a solution!
WebView in Windows Store apps has a method called NavigateToLocalStreamUri(Uri source, IUriToStreamResolver streamResolver) which is designed for loading local HTTP page with external resources, like HTML pages with extern CSS styles or images, that cannot be loaded just by using NavigateToString(Uri). How to use that method for loading local pages is shown in this short article.
In short you create special object implementing the IUriToStreamResolver interface, that has method IAsyncOperation<IInputStream> UriToStreamAsync(Uri uri). In this method you can handle all HTTP requests from the WebView and load your page resources from any source you want!
The solution
Even though the method is called NavigateToLocalStreamUri, it works just fine even when I manually download the requested resources from the Internet in the IUriToStreamResolver object using the HttpClient class. And with the fact, that HttpClient class allows us to ignore specific certificate errors when connecting to HTTPS web sites, we have a solution for our problem!
Here’s a quick and ugly proof of concept for the problem:
public sealed partial class MainPage { public MainPage() { InitializeComponent(); } protected override void OnNavigatedTo(NavigationEventArgs e) { base.OnNavigatedTo(e); Uri uri = new Uri("https://expired.identrustssl.com/"); Uri localStreamUri = Web.BuildLocalStreamUri("MyTag", "/"); BadHttpsStreamResolver resolver = new BadHttpsStreamResolver(uri, localStreamUri); Web.NavigateToLocalStreamUri(localStreamUri, resolver); } } public sealed class BadHttpsStreamResolver : IUriToStreamResolver { private readonly string baseUri; private readonly string localStreamUri; private readonly HttpClient hc; public BadHttpsStreamResolver(Uri baseUri, Uri localStreamUri) { this.baseUri = baseUri.ToString(); this.localStreamUri = localStreamUri.ToString(); HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter(); // specify here which certificate errors should we ignore filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired); hc = new HttpClient(filter); } public IAsyncOperation UriToStreamAsync(Uri uri) { // TODO better uri validation and conversion Uri targetUri = new Uri(uri.ToString().Replace(localStreamUri, baseUri)); return GetInputStream(targetUri).AsAsyncOperation(); } public async Task GetInputStream(Uri targetUri) { try { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, targetUri); HttpResponseMessage response = await hc.SendRequestAsync(request); IInputStream stream = await response.Content.ReadAsInputStreamAsync(); return stream; } catch (Exception ex) { Debug.WriteLine("Exception {0}", ex); return null; } } } |
How this works? We have a WebView named “Web” in the MainPage XAML. We have a sample page https://expired.identrustssl.com/ with expired domain certificate. First we create local stream Uri using the Web.BuildLocalStreamUri(“MyTag”, “/”) method. Then we create the instance of our online IUriToStreamResolver. In our stream resolver we just transform every local Uri to the proper online Uri and use the HttpClient class instance with HttpBaseProtocolFilter ignoring ChainValidationResult.Expired errors for downloading all resources that the page needs.
Here you can see, how the sample page is displayed just fine in my UWP Store app. Note this page uses external CSS file and three external images, all these resources were loaded using my BadHttpsStreamResolver class.
Possible problems with this solution
Sadly this solution is not almighty. It works fine for reasonably simple web sites, but not for highly dynamic sites using AJAX XMLHttpRequest, WebSockets or other advanced stuff. This method also won’t work for POST and other non-GET request on that page.
There is a project called Web Application Template that tries to solve some of these advanced scenarios, I’ve not tried it but it looks quite promising especially for solving the XMLHttpRequest problem by using injected proxy class that redirects all XMLHttpRequest calls into the C# code.
Conclusion
I have shown in this article that it’s possible to display simple HTTPS pages with invalid certificate inside WebView in UWP Windows Store apps. For sites with valid self-signed certificate it’s possible to use whitelisted certificate or certificate root in app manifest, for pages with other certificate validation errors it’s possible to handle all HTTP request with custom IUriToStreamResolver class.
In my opinion the fact, that WebView does not support opt-in for displaying HTTPS pages with invalid certificates is a problem that should be fixed. If you agree with me, please vote for these two UserVoice issues, thanks!
Did you like this article, have you found it helpful, or do you know other methods for displaying HTTPS page with invalid certificate in UWP WebView? Please let me know either here in the discussion or on my Twitter, thanks!