The scrollbars in UWP WebView and in Edge have different default behavior leading to many emails to my team. (Everything I talk about here is for the EdgeHtml based WebView and Edge browser and does not apply to the Chromium based Edge browser and WebView2).
There is a Edge only -ms-overflow-style
CSS property that controls scroll behavior. We have a
different default for this in the WebView as compared to the Edge browser. If you want the appearance of the scrollbar in the WebView to match the browser then you must explicitly set that CSS
property. The Edge browser default is scrollbar
which gives us a Windows desktop styled non-auto-hiding scrollbar. The WebView default is -ms-autohiding-scrollbar
which
gives a sort of compromise between desktop and UWP app scrollbar behavior. In this configuration it is auto-hiding. When used with the mouse you'll get Windows desktop styled scrollbars and when
used with touch you'll get the UWP styled scrollbars.
Since WebViews are intended to be used in apps this style is the default in order to better match the app's scrollbars. However this difference between the browser and WebView has led to confusion.
Here’s an -ms-overflow-style JSFiddle showing the difference between the two styles. Try it in the Edge browser and in WebView. An easy way to try it in the Edge WebView is using the JavaScript Browser.
There are two main differences in terms of security between a JavaScript UWP app and the Edge browser:
A JavaScript UWP app has one process (technically not true with background tasks and other edge cases but ignoring that for the moment) that runs in the corresponding appcontainer defined by the app's appx manifest. This one process is where edgehtml is loaded and is rendering HTML, talking to the network, and executing script. Specifically, the UWP main UI thread is the one where your script is running and calling into WinRT.
In the Edge browser there is a browser process running in the same appcontainer defined by its appx manifest, but there are also tab processes. These tab processes are running in restricted app containers that have fewer appx capabilities. The browser process has XAML loaded and coordinates between tabs and handles some (non-WinRT) brokering from the tab processes. The tab processes load edgehtml and that is where they render HTML, talk to the network and execute script.
There is no way to configure the JavaScript UWP app's process model but using WebViews you can approximate it. You can create out of process WebViews and to some extent configure their capabilities, although not to the same extent as the browser. The WebView processes in this case are similar to the browser's tab processes. See the MSWebViewProcess object for configuring out of process WebView creation. I also implemented out of proc WebView tabs in my JSBrowser fork.
The ApplicationContentUriRules (ACUR) section of the appx manifest lets an application define what URIs are considered app code. See a previous post for the list of ACUR effects.
Notably app code is able to access WinRT APIs. Because of this, DOM security restrictions are loosended to match what is possible with WinRT.
Privileged DOM APIs like geolocation, camera, mic etc require a user prompt in the browser before use. App code does not show the same browser prompt. There still may be an OS prompt – the same prompt that applies to any UWP app, but that’s usually per app not per origin.
App code also gets to use XMLHttpRequest or fetch to access cross origin content. Because UWP apps have separate state, cross origin here might not mean much to an attacker unless your app also has the user login to Facebook or some other interesting cross origin target.
Previously I described Application Content URI Rules (ACUR) parsing and ACUR ordering. This post describes what you get from putting a URI in ACUR.
URIs in the ACUR gain the following which is otherwise unavailable:
URIs in the ACUR that also have full WinRT access additionally gain the following:
Application Content URI Rules (ACUR from now on) defines the bounds on the web that make up a Microsoft Store application. The previous blog post discussed the syntax of the Rule's Match attribute and this time I'll write about the interactions between the Rules elements.
A single ApplicationContentUriRules element may have up to 100 Rule child elements. When determining if a navigation URI matches any of the ACUR the last Rule in the list with a matching match wildcard URI is used. If that Rule is an include rule then the navigation URI is determined to be an application content URI and if that Rule is an exclude rule then the navigation rule is not an application content URI. For example:
Rule Type='include' Match='https://example.com/'/
Rule Type='exclude' Match='https://example.com/'/
Given the above two rules in that order, the navigation URI https://example.com/ is not an application content URI because the last matching rule is the exclude rule. Reverse the order of the rules and get the opposite result.
In addition to determining if a navigation URI is application content or not, a Rule may also confer varying levels of WinRT access via the optional WindowsRuntimeAccess attribute which may be set to 'none', 'allowForWeb', or 'all'. If a navigation URI matches multiple different include rules only the last rule is applied even as it applies to the WindowsRuntimeAccess attribute. For example:
Rule Type='include' Match='https://example.com/' WindowsRuntimeAccess='none'/
Rule Type='include' Match='https://example.com/' WindowsRuntimeAccess='all'/
Given the above two rules in that order, the navigation URI https://example.com/ will have access to all WinRT APIs because the last matching rule wins. Reverse the rule order and the navigation URI https://example.com/ will have no access to WinRT. There is no summation or combining of multiple matching rules - only the last matching rule wins.
Since I had last posted about using Let's Encrypt with NearlyFreeSpeech, NFS has changed their process for setting TLS info. Instead of putting the various files in /home/protected/ssl and submitting an assistance request, now there is a command to submit the certificate info and a webpage for submitting the certificate info.
The webpage is https://members.nearlyfreespeech.net/{username}/sites/{sitename}/add_tls
and has a textbox for you to paste in all the cert info in PEM form into the textbox. The
domain key, the domain certificate, and the Let's Encrypt intermediate cert must be pasted into the textbox and submitted.
Alternatively, that same info may be provided as standard input to nfsn -i set-tls
To renew my certificate with the updated NFS process I followed the commands from Andrei Damian-Fekete's script which depends on acme_tiny.py:
python acme_tiny.py --account-key account.key --csr domain.csr --acme-dir /home/public/.well-known/acme-challenge/ > signed.crt
wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
cat domain.key signed.crt intermediate.pem > chained.pem
nfsn -i set-tls < chained.pem
Because
my certificate had already expired I needed to comment out the section in acme_tiny.py that validates the challenge file. The filenames in the above map to the following:
Parsing WinMD files, the containers of WinRT API metadata, is relatively simple using the appropriate .NET reflection APIs. However, figuring out which reflection APIs to use is not obvious. I've got a completed C sharp class parsing WinMD files that you can check out for reference.
Use System.Reflection.Assembly.ReflectionOnlyLoad
to load the
WinMD file. Don't use the normal load methods because the WinMD files contain only metadata. This will load up info about APIs defined in that WinMD, but any references to types outside of that
WinMD including types found in the normal OS system WinMD files must be resolved by the app code via the System.Reflection.InteropServices.WindowsRuntimeMetadata.ReflectionOnlyNamespaceResolve
event.
In this event handler you must resolve the unknown namespace reference by adding an assembly to the NamespaceResolveEventArgs's ResolvedAssemblies property. If you're only interested in OS system
WinMD files you can use System.Reflection.InteropServices.WindowsRuntimeMetadata.ResolveNamespace
to
turn a namespace into the expected OS system WinMD path and turn that path into an assembly with ReflectionOnlyLoad.
I've put my WPAD Fiddler extension source and the installer on GitHub.
Six years ago I made a WPAD DHCP server Fiddler extension (described previously and previously). The extension runs a WPAD DHCP server telling any clients that connect to connect to the running Fiddler instance. I've finally got around to putting the source on GitHub. I haven't touched it in five or so years so this is either for posterity or education or something.
I've made a PowerShell script to show system toast notifications with WinRT and PowerShell. Along the way I learned several interesting things.
First off calling WinRT from PowerShell involves a strange syntax. If you want to use a class you write [-Class-,-Namespace-,ContentType=WindowsRuntime] first to tell PowerShell about the type. For example here I create a ToastNotification object:
[void][Windows.UI.Notifications.ToastNotification,Windows.UI.Notifications,ContentType=WindowsRuntime];
$toast = New-Object Windows.UI.Notifications.ToastNotification -ArgumentList $xml;
And
here I call the static method CreateToastNotifier on the ToastNotificationManager class:
[void][Windows.UI.Notifications.ToastNotificationManager,Windows.UI.Notifications,ContentType=WindowsRuntime];
$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($AppUserModelId);
With
this I can call WinRT methods and this is enough to show a toast but to handle the click requires a little more work.
To handle the user clicking on the toast I need to listen to the Activated event on the Toast object. However Register-ObjectEvent doesn't handle WinRT events. To work around this I created a .NET event wrapper class to turn the WinRT event into a .NET event that Register-ObjectEvent can handle. This is based on Keith Hill's blog post on calling WinRT async methods in PowerShell. With the event wrapper class I can run the following to subscribe to the event:
function WrapToastEvent {
param($target, $eventName);
Add-Type -Path (Join-Path $myPath "PoshWinRT.dll")
$wrapper = new-object "PoshWinRT.EventWrapper[Windows.UI.Notifications.ToastNotification,System.Object]";
$wrapper.Register($target, $eventName);
}
[void](Register-ObjectEvent -InputObject (WrapToastEvent $toast "Activated") -EventName FireEvent -Action {
...
});
To handle the Activated event I want to put focus back on the PowerShell window that created the toast. To do this I need to call the Win32 function SetForegroundWindow. Doing so from PowerShell is surprisingly easy. First you must tell PowerShell about the function:
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class PInvoke {
[DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hwnd);
}
"@
Then
to call:
[PInvoke]::SetForegroundWindow((Get-Process -id $myWindowPid).MainWindowHandle);
But figuring out the HWND to give to SetForegroundWindow isn't totally straight forward. Get-Process exposes a MainWindowHandle property but if you start a cmd.exe prompt and then run PowerShell inside of that, the PowerShell process has 0 for its MainWindowHandle property. We must follow up process parents until we find one with a MainWindowHandle:
$myWindowPid = $pid;
while ($myWindowPid -gt 0 -and (Get-Process -id $myWindowPid).MainWindowHandle -eq 0) {
$myWindowPid = (gwmi Win32_Process -filter "processid = $($myWindowPid)" | select ParentProcessId).ParentProcessId;
}
[Windows.System.Launcher,Windows.System,ContentType=WindowsRuntime]
$uri = New-Object System.Uri "http://example.com/"
[Windows.System.Launcher]::LaunchUriAsync($uri)
Note
that like using WinRT in .NET, you use the System.Uri .NET class instead of the Windows.Foundation.Uri WinRT class which is not projected and under the covers the system will convert the System.Uri
to a Windows.Foundation.Uri.
2016-Nov-5: Updated post on using Let's Encrypt with NearlyFreeSpeech.net
I use NearlyFreeSpeech.net for my webhosting for my personal website and I've just finished setting up TLS via Let's Encrypt. The process was slightly more complicated than what you'd like from Let's Encrypt. So for those interested in doing the same on NearlyFreeSpeech.net, I've taken the following notes.
The standard Let's Encrypt client requires su/sudo access which is not available on NearlyFreeSpeech.net's servers. Additionally NFSN's webserver doesn't have any Let's Encrypt plugins installed. So I used the Let's Encrypt Without Sudo client. I followed the instructions listed on the tool's page with the addition of providing the "--file-based" parameter to sign_csr.py.
One thing the script doesn't produce is the chain file. But this topic "Let's Encrypt - Quick HOWTO for NSFN" covers how to obtain that:
curl -o domain.chn https://letsencrypt.org/certs/lets-encrypt-x1-cross-signed.pem
Now that you have all the required files, on your NFSN server make the directory /home/protected/ssl and copy your files into it. This is described in the NFSN topic provide certificates to NFSN. After copying the files and setting their permissions as described in the previous link you submit an assistance request. For me it was only 15 minutes later that everything was setup.
After enabling HTTPS I wanted to have all HTTP requests redirect to HTTPS. The normal Apache documentation on how to do this doesn't work on NFSN servers. Instead the NFSN FAQ describes it in "redirect http to https and HSTS". You use the X-Forwarded-Proto instead of the HTTPS variable because of how NFSN's virtual hosting is setup.
RewriteEngine on
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^.*$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]
Turning on HSTS is as simple as adding the HSTS HTTP header. However, the description in the above link didn't work because my site's NFSN realm isn't on the latest Apache yet. Instead I added the following to my .htaccess. After I'm comfortable with everything working well for a few days I'll start turning up the max-age to the recommended minimum value of 180 days.
Header set Strict-Transport-Security "max-age=3600;"
Finally, to turn on CSP I started up Fiddler with my CSP Fiddler extension. It allows me to determine the most restrictive CSP rules I could apply and still have all resources on my page load. From there I found and removed inline script and some content loaded via http and otherwise continued tweaking my site and CSP rules.
After I was done I checked out my site on SSL Lab's SSL Test to see what I might have done wrong or needed improving. The first time I went through these steps I hadn't included the chain file which the SSL Test told me about. I was able to add that file to the same files I had already previously generated from the Let's Encrypt client and do another NFSN assistance request and 15 minutes later the SSL Test had upgraded me from 'B' to 'A'.