gioui-plugins / gio-plugins

Gio-Plugins offers new plugins to extend and enhance your Gio app. Inspired by flutter/plugins repository,
Other
46 stars 6 forks source link

networkSecurityConfig #64

Closed ddkwork closed 2 months ago

ddkwork commented 2 months ago

Affected plugin webview

Describe the bug embed local html jss cs file not has networkSecurityConfig

Expected behavior response 200

To Reproduce Steps to reproduce the behavior:

  1. embed css js html file dir
  2. build apk and install on mobile phone

Reproducible code

networkSecurityConfig

Screenshots If applicable, add screenshots to help explain your problem.

Browser (please complete the following information):

Additional context Add any other context about the problem here.

ddkwork commented 2 months ago

Screenshot_20240405_143611_localhost.demowebviewer.jpg

Screenshot_20240405_142949_com.microsoft.emmx.jpg

ddkwork commented 2 months ago

Also, I'd like to make a pr to upgrade the gio version to 0.5+ if you're willing to merge it, gioui.org v0.5.1-0.20240329172903-1802761c9328 Very arbitrary to get a view of your handle, which makes setting up the dark theme for material 3 on windows easy, and in addition I'm going to be making use of the latest submission of the I will study how to drag and drop files with the help of the window handles obtained from the latest submission.

Translated with DeepL.com (free version)

gedw99 commented 2 months ago

I was also thinking about 0.5+ upgrade.

ddkwork commented 2 months ago

could you please merge pr?

---Original--- From: @.> Date: Fri, Apr 5, 2024 20:41 PM To: @.>; Cc: @.**@.>; Subject: Re: [gioui-plugins/gio-plugins] networkSecurityConfig (Issue #64)

I was also thinking about 0.5+ upgrade.

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

inkeliz commented 2 months ago

You need to run something like:


var appName = `microsoft.win32webviewhost_cw5n1h2txyewy`

func setPrivateConnections(enable bool) error {
      param := `-a`
    if !enable {
        param = `-d`
    }

    // That command MUST run as Administrator, so we are using powershell with `-Verb RunAs`
    // powershell.exe -Command "Start-Process cmd '/c CheckNetIsolation.exe LoopbackExempt -a -n={APP_NAME} > {PATH}'" -Verb RunAs -WindowStyle hidden"
    cmd := exec.Command(
        `powershell.exe`,
        `-Command`,
        `Start-Process cmd '/c CheckNetIsolation.exe LoopbackExempt `+param+` -n=`+appName+`' -Verb RunAs -WindowStyle hidden -Wait`,
    )

    cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}

    if err := cmd.Run(); err != nil {
         panic(err)
    }
}

I removed it from go-plugins, because some malware-detections/anti-virus identifies that CMD string and flag as malware or potentially dangerous. So, if we have one option (like: Config{EnableLocal: true}) then that code will be imported by everyone (dead-code elimination can't remove it). Maybe we can create something similar to installview package, in that case localview and then only who imports that will use it.

EDIT: That requires ADMIN permission to run, but you shouldn't run the app as admin by default. So, that CMD promotes only that command to run as ADMIN.

EDIT2: Now I notice that the issue is about Android, so that solution didn't apply.

inkeliz commented 2 months ago

Upgrade to 0.5 is another topic, that will be more complex and I hope I can migrate everything soon. :\

inkeliz commented 2 months ago

So, I take a look into that. The issue is not actually related to local files, but plain-text. I think you can't connect with any website that don't use TLS/SSL.

Unfortunately, to change that you need to modify the Manifest (to use networkSecurityConfig as you mention). However, it's not possible to change the Manifest without changing gogio directly, or you need to use another "fork" of gogio which is available at https://github.com/gioui/gio-cmd/pull/13. I will try to experiment a bit with that and see with I manage to enable such setting. ;)

ddkwork commented 2 months ago

You need to run something like:


var appName = `microsoft.win32webviewhost_cw5n1h2txyewy`

func setPrivateConnections(enable bool) error {
      param := `-a`
  if !enable {
      param = `-d`
  }

  // That command MUST run as Administrator, so we are using powershell with `-Verb RunAs`
  // powershell.exe -Command "Start-Process cmd '/c CheckNetIsolation.exe LoopbackExempt -a -n={APP_NAME} > {PATH}'" -Verb RunAs -WindowStyle hidden"
  cmd := exec.Command(
      `powershell.exe`,
      `-Command`,
      `Start-Process cmd '/c CheckNetIsolation.exe LoopbackExempt `+param+` -n=`+appName+`' -Verb RunAs -WindowStyle hidden -Wait`,
  )

  cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}

  if err := cmd.Run(); err != nil {
       panic(err)
  }
}

I removed it from go-plugins, because some malware-detections/anti-virus identifies that CMD string and flag as malware or potentially dangerous. So, if we have one option (like: Config{EnableLocal: true}) then that code will be imported by everyone (dead-code elimination can't remove it). Maybe we can create something similar to installview package, in that case localview and then only who imports that will use it.

EDIT: That requires ADMIN permission to run, but you shouldn't run the app as admin by default. So, that CMD promotes only that command to run as ADMIN.

Thank you for your guidance, but I'm a bit curious: this question describes compiling to APK and running it on an Android phone, and you said to run the Windows powershell command. Are you sure this is the case? I'm sorry, my English is very poor and I may not have accurately described it. My screenshot is also displayed on my phone instead of a PC or tablet

inkeliz commented 2 months ago

Untested: One workaround is to serve the localhost using TLS/SSL, with self-signed certificate. Then, use webviewer.SetCustomCertificates() (already available in the current gio-plugins) and set the CA as trusted.

ddkwork commented 2 months ago

So, I take a look into that. The issue is not actually related to local files, but plain-text. I think you can't connect with any website that don't use TLS/SSL.

Unfortunately, to change that you need to modify the Manifest (to use networkSecurityConfig as you mention). However, it's not possible to change the Manifest without changing gogio directly, or you need to use another "fork" of gogio which is available at https://github.com/gioui/gio-cmd/pull/13. I will try to experiment a bit with that and see with I manage to enable such setting. ;)

nice work

ddkwork commented 2 months ago

Untested: One workaround is to serve the localhost using TLS/SSL, with self-signed certificate. Then, use webviewer.SetCustomCertificates() (already available in the current gio-plugins) and set the CA as trusted.

So, do you need a public key and a private key, sir? Could you please create a demo? Thank you

ddkwork commented 2 months ago

I haven't had a chance to carefully study your source code yet. If you want to customize a certificate, theoretically, you need to be able to intercept inbound requests from the webview server, similar to the process of mitmproxy. I checked and found that this error code is due to the higher version of Android's security mechanism not trusting local IPs and only trusting domain names. However, on Android phones, the webview server needs to control its inbound and outbound rules, and its public API has this feature. Do you think so? I am very confused about this. The simplest way is to modify the XML, but directly modify this https://github.com/gioui/gio/blob/main/app%2Fpermission%2Fnetworkstate%2Fmain.go It is also not possible to synchronize later updates

---Original--- From: @.> Date: Sat, Apr 6, 2024 00:59 AM To: @.>; Cc: @.**@.>; Subject: Re: [gioui-plugins/gio-plugins] networkSecurityConfig (Issue #64)

Untested: One workaround is to serve the localhost using TLS/SSL, with self-signed certificate. Then, use webviewer.SetCustomCertificates() (already available in the current gio-plugins) and set the CA as trusted.

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

ddkwork commented 2 months ago

I was thinking as you said: can webview recognize it after changing the local HTTP server to an HTTPS server and setting up a certificate? If that's the case, should this certificate be added to the trusted certificate list on the Android system? Can webview trust the certificate and allow local IP requests at this time? It's a bit troublesome, in that case

---Original--- From: @.> Date: Sat, Apr 6, 2024 00:57 AM To: @.>; Cc: @.**@.>; Subject: Re: [gioui-plugins/gio-plugins] networkSecurityConfig (Issue #64)

So, I take a look into that. The issue is not actually related to local files, but plain-text. I think you can't connect with any website that don't use TLS/SSL.

Unfortunately, to change that you need to modify the Manifest (to use networkSecurityConfig as you mention). However, it's not possible to change the Manifest without changing gogio directly, or you need to use another "fork" of gogio which is available at gioui/gio-cmd#13. I will try to experiment a bit with that and see with I manage to enable such setting. ;)

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

inkeliz commented 2 months ago

You have quite bunch of options to fix that, the difficult will vary based on how you are providing the "localhost".

If Localhost is in Go:

If you are using: http.ListenAndServe() in Go, and then using http://localhostin WebView, you can switch to TLS. You need to create (two) certificates. I already made one package for that, four years ago:

diff --git a/webviewer/demo/demo.go b/webviewer/demo/demo.go
index 5cc3831..5604faa 100644
--- a/webviewer/demo/demo.go
+++ b/webviewer/demo/demo.go
@@ -1,11 +1,16 @@
 package main

 import (
+   "crypto/rand"
+   "crypto/tls"
+   "crypto/x509"
    "flag"
    "gioui.org/font"
+   "github.com/inkeliz/inkcert"
    "image"
    "image/color"
    "math"
+   "net/http"
    "net/url"
    "os"
    "time"
@@ -30,7 +35,7 @@ import (

 var (
    GlobalShaper = text.NewShaper(text.WithCollection(gofont.Collection()))
-   DefaultURL   = "https://google.com"
+   DefaultURL   = "https://127.0.0.1:8000"

    IconAdd, _            = widget.NewIcon(icons.ContentAdd)
    IconClose, _          = widget.NewIcon(icons.NavigationClose)
@@ -41,6 +46,26 @@ var (
    IconJavascript, _     = widget.NewIcon(icons.AVPlayArrow)
 )

+var genCert = inkcert.NewServer(rand.Reader, &inkcert.Info{
+   Organization:  "Internet",
+   Country:       "Internet",
+   Province:      "Internet",
+   Locality:      "Internet",
+   StreetAddress: "Internet",
+   PostalCode:    "Internet",
+})
+
+func init() {
+   cert, err := x509.ParseCertificate(genCert.CADer)
+   if err != nil {
+       panic(err)
+   }
+
+   if err := webview.SetCustomCertificates([]*x509.Certificate{cert}); err != nil {
+       panic(err)
+   }
+}
+
 func main() {
    proxy := flag.String("proxy", "", "proxy")
    if proxy != nil && *proxy != "" {
@@ -54,6 +79,25 @@ func main() {
    }
    flag.Parse()

+   mux := http.NewServeMux()
+   mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+       w.Write([]byte("<html><h1>Hello, World!</h1></html>"))
+   })
+
+   localServer := &http.Server{
+       TLSConfig: &tls.Config{
+           GetCertificate: genCert.TLSGetCertificate,
+       },
+       Handler: mux,
+       Addr:    ":8000",
+   }
+
+   go func() {
+       if err := localServer.ListenAndServeTLS("", ""); err != nil {
+           panic(err)
+       }
+   }()
+
    webview.SetDebug(true)
    window := app.NewWindow()

So, the idea is: serving it as TLS and trust the CA, which is generated at runtime.


If you don't have control of "Localhost Server", you can proxy it:

diff --git a/webviewer/demo/demo.go b/webviewer/demo/demo.go
index 5cc3831..98a5962 100644
--- a/webviewer/demo/demo.go
+++ b/webviewer/demo/demo.go
@@ -1,11 +1,17 @@
 package main

 import (
+   "crypto/rand"
+   "crypto/tls"
+   "crypto/x509"
    "flag"
    "gioui.org/font"
+   "github.com/inkeliz/inkcert"
    "image"
    "image/color"
    "math"
+   "net/http"
+   "net/http/httputil"
    "net/url"
    "os"
    "time"
@@ -30,7 +36,7 @@ import (

 var (
    GlobalShaper = text.NewShaper(text.WithCollection(gofont.Collection()))
-   DefaultURL   = "https://google.com"
+   DefaultURL   = "https://127.0.0.1:8000"

    IconAdd, _            = widget.NewIcon(icons.ContentAdd)
    IconClose, _          = widget.NewIcon(icons.NavigationClose)
@@ -41,6 +47,26 @@ var (
    IconJavascript, _     = widget.NewIcon(icons.AVPlayArrow)
 )

+var genCert = inkcert.NewServer(rand.Reader, &inkcert.Info{
+   Organization:  "Internet",
+   Country:       "Internet",
+   Province:      "Internet",
+   Locality:      "Internet",
+   StreetAddress: "Internet",
+   PostalCode:    "Internet",
+})
+
+func init() {
+   cert, err := x509.ParseCertificate(genCert.CADer)
+   if err != nil {
+       panic(err)
+   }
+
+   if err := webview.SetCustomCertificates([]*x509.Certificate{cert}); err != nil {
+       panic(err)
+   }
+}
+
 func main() {
    proxy := flag.String("proxy", "", "proxy")
    if proxy != nil && *proxy != "" {
@@ -54,6 +80,36 @@ func main() {
    }
    flag.Parse()

+   // Emulating the localhost that you can't control:
+   muxExternalLocal := http.NewServeMux()
+   muxExternalLocal.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+       w.Write([]byte("<html><h1>Hello, World from HTTP!</h1></html>"))
+   })
+   go func() {
+       if err := http.ListenAndServe(":4242", muxExternalLocal); err != nil {
+           panic(err)
+       }
+   }()
+   // -----------------------------------------
+
+   localServer := &http.Server{
+       TLSConfig: &tls.Config{
+           GetCertificate: genCert.TLSGetCertificate,
+       },
+       Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+           r.URL.Scheme = "http"
+           r.URL.Host = "localhost:4242" // The localhost that you can't control
+           httputil.NewSingleHostReverseProxy(r.URL).ServeHTTP(w, r)
+       }),
+       Addr: ":8000",
+   }
+
+   go func() {
+       if err := localServer.ListenAndServeTLS("", ""); err != nil {
+           panic(err)
+       }
+   }()
+
    webview.SetDebug(true)
    window := app.NewWindow()

In that case, the :4242 is the Localhost (which is extenral to Go, or it's other HTTP server in your network). That creates the :8000 server which calls :4242. It's a basic proxy. If you want to "proxy everything" then it's also possible, by using SetProxy, but you need to implement one HTTP Proxy server in Go (which can be quite complex).


Another option is patching gogio, either manually or something "better" to get merge:

You can change https://github.com/gioui/gio-cmd/blob/main/gogio/androidbuild.go#L458 and add android:usesCleartextTraffic="true". I didn't test it, but maybe it will work.

ddkwork commented 2 months ago

i will check soon ,thx

ddkwork commented 2 months ago

bug not passed

inkeliz commented 2 months ago

In the screenshot the URL seems to be http://, it should be https://. In case of Windows, you should enable network connection (that I mention in the first response).

ddkwork commented 2 months ago

In the screenshot the URL seems to be http://, it should be https://. In case of Windows, you should enable network connection (that I mention in the first response).

Gosh, it worked, I was careless, thank you for the great project, and the next thing is to look at the question of how go communicates with js

Screenshot_20240407_011045_localhost.demowebviewer.jpg

ddkwork commented 2 months ago

I'll do a reverse proxy test soon

ddkwork commented 2 months ago

soonandroid:usesCleartextTraffic="true" Screenshot_20240407_122527_localhost.demowebviewer.jpg

not passed,it's working now,so i will close it, thx again.