I want to test service workers but I have a virtual host setup and I can't seem to be able to enable https on localhost.
How can I whitelist my local virtual host url to test service workers
whenever I try and register for the service worker on the localhost? Chrome says https is required to enable service worker. How can I get past this restriction at least for local testing.
In general, you need to serve both your page and your service worker script via HTTPS in order to use service workers. The rationale is described at Prefer Secure Origins For Powerful New Features.
There's an exception to the HTTPS requirement in place to facilitate local development: if you access your page and service worker script via http://localhost[:port], or via http://127.x.y.z[:port], then service workers should be enabled without any further actions.
In recent versions of Chrome, you can work around this requirement duriing local development via chrome://flags/#unsafely-treat-insecure-origin-as-secure, as explained in this answer.
Firefox offers similar functionality, via the devtools.serviceWorkers.testing.enabled setting.
Please note that this functionality is only meant to facilitate testing that wouldn't otherwise be able to take place, and you should always plan on using HTTPS when serving the production version of your site. Don't ask real users to go through the steps of enabling those flags!
If you want to debug a plugged-in mobile device's service worker for a real behavior testing of a progressive web app, the ssl chrome start options do not help and you definitely do not need to buy certificates.
#chris-ruppel mentioned installing proxy software, but there is actually an easier way using port forwarding:
Assuming you connect and debug your device using Chrome:
In the Chrome Dev Tools "Remote devices" open "Settings" and add a "Port forwarding" rule.
If your localhost setup is running on localhost:80,
just add a rule "Device port 8080" (can be any unpriviliged port > 1024)
and local address "localhost:80" (or mytestserver.sometestdomainwithoutssl.company:8181 or whatever)
After you did that, you can call the URL "http://localhost:8080" on your mobile device and it will be answered by the "localhost:80" on your actual PC/test server. Works perfectly with service workers as if it were your local machine running on your mobile.
Works also for multiple port forwardings and different target domains as long as you remember to use unprivileged ports on your mobile device. See screenshot:
Source of this info is the google remote devices documentation: https://developers.google.com/web/tools/chrome-devtools/remote-debugging/local-server
(but as of Apr 2017 it is not very clear to read this simple answer out of it)
I often want to debug and test on a real device. One method I've come up with involves routing the phone's network traffic through Charles Proxy during local development. Unlike all the Chrome-specific solutions, this works with any browser on your phone.
Run Charles on my laptop (which also serves my website with the Service Worker). Once Charles is running, note the IP/port for Step 2.
Configure the mobile device to use my laptop as a proxy.
For Android just tap and hold on your WiFi in settings > Modify network > Advanced Settings > Proxy. Use Manual to set the IP/port.
For iOS click the (i) icon > HTTP Proxy section. Select Manual, then set the IP/port.
Visiting localhost on my mobile device now allows the Service Worker to be registered and tested.
The easiest way to test pwa, in my case, was using ngrok.
https://ngrok.com/download log in, get ur token and set it!
When you run ./ngrok http {your server port} make sure that you use https which will be shown in the terminal after you run this command above.
You could use https://surge.sh too, it is for host a static webpage, if you visit here: https://surge.sh/help/securing-your-custom-domain-with-ssl
will be able to see how to set up a ssl certificate
I used ngrok to tunnel the local IP (it is really not that because it is on Google Colab) to a public one.
Going to the ngrok console I can see all the tunnels created. I created just one tunnel for localhost:port but here there are 2, one for HTTP and other for HTTPS (isn't that nice?).
If I go to the https address of my Web App, on the console I see
But if I go to the http address, on the console I get
Q: Can you work with service workers that need HTTPs through tunnels to a remote machine?
A: Apparently yes!
The code behind that registration is (important to know where it fails):
// Here we register the SERVICE WORKER
IndexController.prototype._registerServiceWorker = function() {
console.log("1.Starting SW function.");
if (!navigator.serviceWorker) {
console.log("2.Browser is NOT compatible with SW or something is not working.");
return; }
console.log("2.Browser is compatible with SW.");
navigator.serviceWorker.register('/sw.js').then(function() {
console.log('3.Registration worked!');
}).catch(function() {
console.log('3.Registration failed!');
});
};
And to make it more complicated, my web App using Service Workers is running inside Colab (Google Colab). The web App is running on Node.js inside Colab.
If you are working from localhost it should be easier for you, because the https requirement is not enforced when connecting to localhost (according to theory). [A] and [B]
That is not the same that the browser will be nice with your App just because it runs on localhost.
Note: My experiment above..
Firefox: worked with and without the settings below.
Chrome: Without adding the urls to the whitelist and restartting I got
Going to https web app I got:
IndexController.js:49 Mixed Content: The page at 'https://0a4e1e0095b0.ngrok.io/' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://0a4e1e0095b0.ngrok.io/updates?since=1602934673264&'. This request has been blocked; this endpoint must be available over WSS.
IndexController._openSocket # IndexController.js:49
IndexController # IndexController.js:10
(anonymous) # index.js:16
loadScripts # loadScripts.js:5
46.../utils/loadScripts # index.js:15
s # _prelude.js:1
e # _prelude.js:1
(anonymous) # _prelude.js:1
IndexController.js:49 Uncaught DOMException: Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.
at IndexController._openSocket (https://0a4e1e0095b0.ngrok.io/js/main.js:2251:12)
Going to http web app I got:
Navigated to http://0a4e1e0095b0.ngrok.io/
IndexController.js:17 1.Starting SW function.
IndexController.js:19 2.Browser is NOT compatible with SW or something is not working.
If you are not on localhost AND can't use https, then you may need to change these settings on your browser.
Some people already explained this but here it goes again.
Chrome:
Go to chrome://flags/#unsafely-treat-insecure-origin-as-secure
Add the urls you want to whitelist.
Restart chrome
Note that this will restart all Chrome windows. This is not a solution for me because my tunnels change name every time they are created and I can't be restarting a bunch of windows every time.
Firefox / Waterfox
Open Developer Tools
Open settings
Mark "Enable service workers over HTTP (when toolbox is open)"
Firefox/Waterfox
You probably don't need to do the changes below, but I did (my browser may be a bit old). More info here.
In about:config
I enabled
dom.serviceWorkers.testing.enabled
dom.serviceWorkers.enabled
I highly recommend looking at this https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers and related pages on that same site.
If someone is interested in the ngrok setup, it is very simple (python version).
# Install pyngrok python package on your Google Colab Session
!pip install pyngrok
# Set up your ngrok Authtoken (requires free registration)
!ngrok authtoken YOUR_TOKEN_HERE
# Invoke ngrok from Python and start tunneling/connecting
from pyngrok import ngrok
# Open a HTTP tunnel on the default port 80 if not specified
ngrok_tunnel = ngrok.connect('8888')
# You can print it, or go to the ngrok console on https://dashboard.ngrok.com/status/tunnels
print (ngrok_tunnel.public_url)
If you want to test service workers on a client device that cannot run a web server on localhost, the general technique is as follows:
Give your server a hostname.
Give this hostname a certificate.
Make IPs trust the CA that issued this certificate.
But this is easier said than done. In a November 2016 AMA on Reddit, a Let's Encrypt representative acknowledged that HTTPS on a private LAN "is a really tough question, and I think no one has come up with a satisfactory answer so far."
Common ways to give your computer a hostname involve giving it a stable internal IP address, not one that changes daily or every time you power-cycle your Internet gateway appliance. You'll need to configure the DHCP server on your network, usually that in your gateway, to set up a "reservation" that associates a particular private address (usualy within 10/8 or 192.168/16) with the MAC address of your development workstation's Ethernet card. For this, read the manual of your gateway.
Now that your development workstation has a stable IP address, there's a time/money tradeoff. If you're willing to learn advanced DNS and OpenSSL usage and install a root certificate on all devices with which you plan to test:
Run an internal DNS server on your network. This could be on your gateway or on your development workstation.
Configure your DNS server to be authoritative for some made-up TLD and recursive for other TLDs.
Give a stable name to your development workstation's private IP address. This gives it an internal name.
Configure your DHCP server to give the address of this DNS server to other devices obtaining leases.
On your development workstation, use OpenSSL to generate keypairs for a private certificate authority and the web server.
Using OpenSSL, issue a root certificate for the CA and a certificate for the web server's internal name.
Configure HTTPS in the web server on your development workstation using this certificate.
Install the CA's root certificate as a trusted root certificate on all devices.
On all devices, access this internal name.
If you cannot add a root certificate or control local DNS, such as if you plan to test with devices owned by others (BYOD) or with more locked-down browsers that do not allow users to add trusted root certificates, such as those in major video game consoles, you'll need a fully-qualified domain name (FQDN):
Buy a domain from a registrar that offers DNS with an API. This could be directly within a TLD or from one of the dynamic DNS providers that has made it onto the Public Suffix List. (Non-PSL dynamic DNS providers are unacceptable because of rate limits imposed by Let's Encrypt.)
In this domain's zone file, point an A record at your development workstation's private IP address. This gives your development workstation a FQDN.
Use Dehydrated, an ACME client that supports the dns-01 challenge, to obtain a certificate for this FQDN from the Let's Encrypt certificate authority.
Configure HTTPS in the web server on your development workstation using this certificate.
On all devices, access this name.
As Jeff mentioned in the first response, you do not need https at the localhost level to test Service Workers. Service workers will register and work just fine as long as you access the localhost domain -- without HTTPS.
Once you have your application tested on localhost and you want to see how it works with https for real, the simplest approach would be to upload your app to GitHub. You can create a public domain for free (and with HTTPS!).
Here are the instructions: https://pages.github.com/
I think the easiest way to test service worker is to find a free hosting provider.
nowadays, there many sites that provide free hosting. you can easily host your app on this free servers.
I mostly use heroku and netlify. this are free and easy to use.
For mobile testing with Android over the network with plain HTTP, a simple solution is to install Termux on your phone, then use socat to port-forward from a local port to your development host:
socat tcp-l:8000,fork,reuseaddr tcp:192.168.1.170:8000
This will run in the background while the terminal is running. Then in Chrome you can connect over plain HTTP to http://127.0.0.1:8000.
Works great over the local network and no certs, apps or services needed.
Related
I deployed a web application which require to send request and get response from a local ip(ie. 192.168.0.123), which is actually a payment terminal.
It seems that this violate the entire https purposes, however I have no control on the payment terminal and it's address due to 3rd parties API.
Are there any workaround to handle localhost/ local ip ith https request?
If you can install additional software on the payment terminal, then you could install another HTTP server that acts as a reverse proxy and listens on HTTPS.
You then make your requests to https://192.168.0.123 and your reverse proxy forwards them to its http://localhost.
Nginx is a popular server for this purpose.
If you can't install additional software then you should look at using an isolated network instead.
It doesn't really matter if communication over a local network isn't encrypted if you completely trust every device that is connected to it.
I am building a website, which access a Philips HUE bridge over local network.
The site is https.
My problem is, that the certificate of the bridge is self-signed, which causes an error when I try to call it:
Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID
From the documentation:
This means that in case you would access the bridge using a browser (e.g. going to https://) the browser will complain that the connection is not secure due to the fact that the certificate has not been certified by a trusted third party, and will ask you whether you want to continue and trust the certificate.
For your application it is best practice to pin (with the bridge-id) the certificate on first connection with the bridge (“trust on first use”) and check upon later contacts with the same bridge.
So the error is expected. But how can I get around it?
I have little experience with certificates, and https for that matter.
Any help would be appreciated.
Currently I am working on Quality control project. The requirement is that i want location from where user entering data that location By using java script , I got on local server but whenever I hos my website on main Server there is error that
After Chrome 50 version it requires HTTPS protocol only , for that i want to add HTTPS protocol on server.After adding SSL certificate it not works.
You need to setup SSL feature on your server. There is How to Set Up SSL on IIS 7 manual about about. Create an SSL binding on your web-node and configure SSL settings after adding SSL certificate.
It is also need to use trusted certificate authorities and setting up certificate chain right to achive "This Site is Safe" checkmark in a browser. Detailed instruction you can get from your certificate issuer.
I'm using an arduino UNO and an Ethernet Shield to create a web server to response HTTP requests.
The requests are sent by ajax XMLHttpRequest.
It's working fine with static ip address.
But a want the arduindo to get a DHCP ip, so I can use it in any local network (with DHCP).
I want to discover the ip of the arduino connected on the local network.
So I can use it as url to send HTTP requests.
Is it possible to do that in javascript?
OK, thanks for answering my questions above. That helped layout the network structure and the problem you're trying to solve.
Summary of the problem
Two computers: 1 web server whose address is dynamic (DHCP) and 1 web client running AJAX and HTML. How can browser find DHCP server?
Options
This is a classic problem solved many different ways throughout the history of computer networks. I've suggested some options below.
Scan for the server via TCP.
Scan for the server via UDP (requires special browser library).
Run a DNS server.
Have Arduino signal its IP.
Modify your router.
Don't run DHCP - use a static IP.
tl;dr - Use Option 6 if you don't control your router, Option 5 if you do.
Option: Scan for the server
I'm assuming you know your browser machine's IP address (for example, 192.168.1.17). In this case, run through all of the address from 192.168.1.0 through 192.168.1.254 (not .255 and skip you browser machine's IP) testing for a connection to port 80. This will find every web server on your subnet, so be aware you will need a way to recognize your arduino responded to the web request in case some other web server is also listening on the network. It will also take some time to set up, test and wait for timeouts on most (252) of the addresses which don't have web servers. You will eventually find it.
I'm not a fan of this one, but it gets the job done. Warning: if you don't "own" the network, someone may be angry with you for scanning their machines. A company, school or other institution may have policies about not scanning networks.
Option: UDP from the browser
This one is great, but requires a browser plug-in and some fancy coding. UDP allows one to broadcast a message to your subnet (try ping 255.255.255.255 at a command line and watch the machines echo back their IPs). If the arduino is set to listen for UDP packets on a particular port, it can echo back to the sender of the UDP packet and let that user know it's present. This is how DNS, DHCP, ping and Apple's Bonjour work. Many IP based systems advertise services by responding to UDP requests. Clients need not know the address of the servers on the subnet, they discover them through broadcast messages on well-known UDP ports.
Unfortunately, this requires a browser modification because Javascript does not support UDP for security reasons. I understand this and agree with the security restriction. However, it has cut out a really nice feature of dynamic service discovery. If you're on Apple on the browser, you might be able to find a Bonjour emulator you can run on the Arduino and it might work ("arduino.local" might attach). This might be possible with Windows service discovery, too, if you're using a Windows client for your browser. I don't know what's available in Linux for service discovery.
I don't like any of these modifications for you. Browser, Arduino (Apple, Windows or Linux), just because it adds more moving parts and you're counting on the browser to "know" how to find the service.
Option: Run a DNS server
This isn't as bad as it sounds, but I'm not sure the Arduino could handle it. Find a very small DNS server written for the Arduino and have it respond to DNS requests. On the browser, look for a well-known machine name (e.g. "my-arduino.lan"). This essentially finesses the UDP problem above by making the Arduino the UDP server (handles DNS requests) and the browser already has name resolution s/w (like every machine on the planet).
I didn't search for Arduino DNS code, it might be too large for the Arduino and writing it may be a real PITA (pain in the butt).
Option: Arduino signals IP
In this model, you can attach a LCD to the arduino and have it present its IP address on the display. Alternatively, the arduino can send a message (via TCP) to a well known server on the network (internet or otherwise) reporting its IP address there. Your browser can query that server, pick up the IP address and then contact the Arduino directly. This introduces a 3rd machine and acts like your own hacked form of DDNS (look it up, if you're not familiar with it).
Option: Modify the router
If you own the router, you can modify the router to assign a specific IP address to the Arduino, even with DHCP. This is your best bet. Here, you control the network, can allow the Arduino to come up in DHCP while still fixing its IP address. You'll have to go through your router API (web or CLI) and figure out how to do it, it's a bit hard directing you as there are thousands of types of commercial and SOHO routers.
If you don't control the router...
Option: Use Static IP
This option is really your best. Give up on DHCP and just set the static IP of the arduino. Just make sure the IP address you pick doesn't conflict with any other servers on the subnet. That shouldn't be difficult.
I hope this helps.
The 2 best options I think:
1) When arduino starts, gets an IP address from DHCP, arduino should make a connection to a well know service provided by you. Also, each device should have an ID, defined by you during manufacturing, like mac address or part of mac address.
So, you can print on the box something like: 5c4e6f.my-well-know-host.com
Than, as I was suggesting, each time arduino starts, it tries to connect to that service passing parameters like:
POST www.my-well-know-host.com
ID: 5c4e6f
IP: 192.168.1.55
than, at that service, you update a DNS table to reflect this relation:
5c4e6f.my-well-know-host.com -> resolves to 192.168.1.55
obviously, from anywhere in the world the host 5c4e6f.my-well-know-host.com will resolve to 192.168.1.55, but you will only access it from you local network.
Tip: this is some kind of DDNS, but with network discovery purposes.
Tip2: there is an linux dns service called MyDNS, where the hosts are simply records inside a MySQL table, easy to maintain.
2) Network discovery
I don't know if arduino is capable to do it, but, the idea is to make arduino listen on a specific UDP port, like 4444, on any address.
So, you can build a windows app, and Android APP (I already made one android discovery for another purpose, not arduino), or, the best solution that I still researching on, is to make a custom page with some javascript code, that "looks" for devices listening on that specific port.
Works like that:
Device gets IP from DHCP
device starts a thread listening for broadcast packets on port 4444
a discovery app listen on another port, like 4445.
the discovery app announces itself using a broadcast packet to 4444 port (255.255.255.255:4444)
each device listening, reports back with its identification and IP to app port 4445.
This is a code to find the server ip:
<script type="text/javascript">
var ip = "<?php echo $_SERVER['SERVER_ADDR']; ?>";
alert(ip);
</script>
If you are using Johnny Five framework, then you can find APIs in that framework
I have a desktop product which uses an embedded webserver which will use self-signed certs.
Is there something that I can put in a web page that would detect that they haven't added the root CA to their trusted list, and display a link or DIV or something directing them how to do it?
I'm thinking maybe a DIV that has instructions on install the CA, and a Javascript that runs some test (tries to access something without internal warnings??), and hides the DIV if the test succeeds. Or something like that...
Any ideas from the brilliant SO community ? :)
Why do you want to do this? It is a bad idea to train users to indiscriminately install root CA certificates just because a web site tells them to. You are undermining the entire chain of trust. A security conscious user would ignore your advice to install the certificate, and might conclude that you are not taking security seriously since you did not bother to acquire a certificate from an existing CA.
Do you really need HTTPS? If so, you should probably bite the bullet and make a deal with a CA to facilitate providing your customers with proper CA signed server certificates. If the web server is only used for local connections from the desktop app, you should either add the self-signed certificate to the trusted list as part of the installation process, or switch to HTTP instead.
Assuming you know C# and you want to install a pfx file.Create a exe that will be run from a url.Follow this URL
and this
The only idea I have is to use frames and some javascript.
The first element of the frame will act as a watchdog waiting x amount of time (javascript setTimeout) before showing your custom ssl failure message to the user with hyperlinks or instructions to download the self-signed cert.
The second frame element attempts the https connection and if successful resets the watchdog frame so that it never fires. If it fails (assume https cert validation failed) the watchdog message would then fire and be presented to the user.
Depending on your browser you will most likely still see some security warning with the approach but you would at least be able to push your own content without requiring users to run untrusted code with no proper trust chain (This would be much much worse from a security POV than accepting the cert validation errors and establishing an untrusted ssl session)
Improvements to the concept may be possible using other testing methods such as XMLHttpRequest et al.
You should not do this. Root certificates are not something you just install, since adding one could compromise any security given to you by https.
However if you are making a desktop app then just only listen to 127.0.0.1. That way the traffic never leaves the users computer and no attacker can listen in.
You might try to add some (hidden) Flex element or Java Applet once per user session.
It will just download any https page of your server and will get all information about connection:
com.sun.deploy.security.CertificateHostnameVerifier.verify()
or
javax.security.cert.X509Certificate.checkValidity()
I suppose Flex (which is more common to users) shoul have similar ways of validating https certificate from user's point of view. It should also share OS' trusted cert. store while Java might have its own.
Since the server is running on the client machine (desktop product) can it not check the supported browsers for installed certs using winapi/os functions? I know Firefox has a cert database in the user's profile directory and IE probably keeps information in the registry. It wouldn't be reliable for all browsers but if the server simply chooses between "Certificate Found" and "Please ensure you have installed the cert before continuing" then no harm is done as the user can choose to continue either way.
You could also simplify matters by providing an embedded browser as well (ie, gecko), this way you only have 1 browser to deal with which simplifies a lot of things (including pre-installing the root CA).
To recap: you are setting up webservers on desktop apps; each desktop will have its own webserver, but you want to use SSL to secure the connection to that webserver.
I guess there are several problems here with certificates, one being that the hostname used to access the desktop has to match the certificate. In this case you have little choice but to generate certificates on the client. You'll need to allow the user some way to specify the host name in case the name used by outsiders can't be detected from the host itself.
I'd also suggest allowing for an admin to install a trusted cert, for those who don't want to rely on self-signed certs. This way you can also offload the cost of trusted cert maintenance to the admins who really want it.
Finally, in my experience browsers either allow or refuse the self-signed cert and there is no way for the server to know if the cert is denied, or temporarily accepted, or permanently accepted. I assume there must be a mechanism somewhere to handle SSL failures but typical web programming doesn't operate at that layer. In any case, the only thing a webserver can do if SSL fails is to fallback to non-SSL, and you've indicated in a comment that you can't have anything non-SSL. I think you should try to have that restriction lifted; a non-SSL start page would be extremely helpful in this situation: it can test (using frames or images or JSON or AJAX) the https connection, and it can link to documentation about how to set up the certificate, or where to download an installer for the cert.
If the browser won't connect because of a self-signed cert, and you're not allowed to use plain HTTP at all, by what other means could you communicate with the user? There are no other channels and you can't establish one because you don't have any communication.
You mentioned in a comment writing a win32 app for installing the cert. You could install a cert at the time you install the application itself, but that doesn't help any remote browsers, and a local browser doesn't need SSL to access localhost.
We've been working on an opensource JavaScript project, called Forge, that's related to this problem. Do you have a website that your users could access? If so, then you could provide a secure connection to those desktop apps via your website using a combination of Flash for cross-domain + JavaScript for TLS. It will require you to implement some web services on your website to handle signing certificates the desktop app certificates (or having your desktop apps upload the self-signed certs so they can be accessed via JavaScript). We describe how it works here:
http://blog.digitalbazaar.com/2010/07/20/javascript-tls-1/
An alternative to setting up a website, but is less secure because it allows for a MiTM attack is to host the JavaScript+Flash directly on the desktop app server. You could have your users hit your desktop app over regular http to download the JS+Flash+SSL cert, but then start using TLS afterwards via the JS. If you're on a localhost connection the MiTM attack might be a little less worrisome -- perhaps enough for you to consider this option.
An ActiveX control could do the trick. But I really didn't chime in to help with the solution, more to disagree with the stance that what you are doing is a security risk.
To be clear, you are needing a secure cipher (hopefully AES and not DES), and are already in control of your endpoints, just not able to completely rule out promiscuous-mode network sniffers that could catch clear-text passwords or other sensitive data.
SSL is a "Secure Socket Layer", and by definition, is NOT dependent upon ANY certificates.
However, all effective modern ciphers require it to authenticate the tunnel endpoints, which is not always a necessity for every application; a frustration I have dealt with in numerous back-end datacenter automation routines using web service APIs to manage nodes, where the "users" were actually processes that needed encrypted key exchange prior to a RESTful command negotiation.
In my case, the VLANs were secured via ACLs, so I really "could" send clear-text authentication headers. But just typing that made me throw up in my mouth a little bit.
I'm sure I'll get flamed for typing this, but I'm extremely battle-hardened and would've made the same comments to you in years 10-15 of my IT career. So I empathize with their worries, and very much appreciate if they are passionate enough about security to flame me. They'll figure it out eventually.....
But I do agree with the fact that it is a BAD idea to "train" users to install root CA's on their own. On the other hand, if you use a self-signed cert, you have to train them to install that. And if a user doesn't know how to determine if a CA Cert is trustworthy, they definitely won't be able to determine a self-signed cert from a CA Cert, and thus either process would have the same effect.
If it were me, I would automate the process instead of having it assist the end-users, so that it becomes as hidden from them as possible, just like a proper PKI would do for an enterprise.
Speaking of which, I just thought of a potential solution. Use the Microsoft PKI Model. With Server 2012 R2, you can deliver trusted keys to endpoints that are not even domain members using "device control" via "workspaces", and the client machines can subscribe to multiple workspaces, so they are not committed solely to yours if they subscribe. Once they do, and authenticate, the AD Certificate Services Role will push all root CA Certs necessary, as are present in active directory, or specified LDAP server. (In case you are using offline CA servers)
Also, I realize this thread is like 7 years old, but am sure it still gets referenced by a good number of people needing similar solutions, and felt obligated to share a contrasting opinion. (Ok Microsoft, where's my kickback for the plug I gave you?)
-cashman