shadowsocks/v2ray-plugin-android

Certificate is not working for TLS with self-signed .cer

ccloli opened this issue · 28 comments

If the server side using a self-signed certificate, and the certificate using an invalid host name, the client cannot connect to the server, as the server side logs remote error: tls: bad certificate.

For example, I configured a server on 1.2.3.4, and self-signed a certificate and its host name is example.com (but I'm not holding it, so accessing the domain directly won't be my server). Then on the client side, I set the server to 1.2.3.4, and specified Hostname in plugin options as example.com, and paste the base64-encoded certificate content. Then I started the connection, but it's not working, server-side says remote error: tls: bad certificate.

The same configuration is working on Windows by importing the .cer file to trusted root certificate, and working on iOS by ignoring SSL error.

I recently also met this problem, the root cause should lie in how this plugin handles certRaw generated from user inputs on Android, is there a way to show the data received by shadowsocks-android from this plugin?

Already fixed in the upstream shadowsocks/v2ray-plugin#128

@madeye Please make a new release?

The root cause was well-explained in the pull request.

Although I think it should be fixed on the caller side (e.g. Android app / WinGUI)...
It's difficult to set a multiline env/parameter right.
(see ProxyInstance.kt#L117, it stores the plugin config into JSON, which does not allow real line-breaks)
For the WinGUI, it's the UI design. (I would recommend you to pass the file instead of the content)

Maybe we should obsolete JSON, and start to use YAML.
Anyway, it the current fix for Android.

Mygod commented

JSON supports line break properly (have you tried \n). Personally I don't see why YAML is better than JSON. I would in fact argue for the opposite as JSON is typed.

FYI, https://www.gun.io/blog/multi-line-strings-in-json .
\n will not be treated as an escape sequence in JSON.
You can process those \n into line breaks after decoding the JSON. But that would not be standard behavior.

I agree with @Mygod , it's OK to use \n in JSON string.

Also when I tested self-signed certificate using the build from this https://circleci.com/gh/shadowsocks/v2ray-plugin/20#artifacts, I tried both cert and certRaw(MUST use the original format including all the \n from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE-----, basically it just means you open the certificate file and copy all the content without any modifications, then paste it after certRaw=, it might look ugly but it works) parameters both on FreeBSD, they all work perfectly.

But when I tried the exactly same settings on Android, it never succeeds.

I agree with @Mygod , it's OK to use \n in JSON string.

Also when I tested self-signed certificate using the build from this https://circleci.com/gh/shadowsocks/v2ray-plugin/20#artifacts, I tried both cert and certRaw(MUST use the original format including all the \n from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE-----, basically it just means you open the certificate file and copy all the content without any modifications, then paste it after certRaw=, it might look ugly but it works) parameters both on FreeBSD, they all work perfectly.

But when I tried the exactly same settings on Android, it never succeeds.

I don't know which version of plugin and which implementation of ss are you testing.

Have you tried a JSON validator before use it?

\n will not be treated as an escape sequence in JSON.

@lixin9311 JSON comes from JavaScript, and in JavaScript, \n is the line break. And from JSON spec, it lists \n as linefeed escape character, so using \n in JSON is fine.

sry, my bad, I mean it should be:

{
  "var" : "first line
second line"
}

you mean:

{
  "var" : "first line\nsecond line"
}

The root cause should be this:

After user inputs the PEM format certificate, the following code removes all the \n in it, which is wrong. To generate valid certificates from the PEM format certificate, all those \ns are necessary.

v2ray-plugin-android/app/src/main/java/com/github/shadowsocks/plugin/v2ray/ConfigFragment.kt#L66

        putWithDefault("certRaw", certRaw.text?.replace("\n", ""), "")

In addition, the following code also prevents from passing in the original PEM format certificate through plugin options.

shadowsocks-android/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.kt#L41

        check(options.all { !it.isISOControl() }) { "No control characters allowed." }

In order to fix this we need to come up with a solution to safely pass PEM format certificate. I have thought about using Base64 to encode/decode the certificate, what do you guys think?

@Mygod

Mygod commented

We cannot allow control characters. We could try removing the begin end lines and add it later.

The problem is not only about the begin end lines, removing all the \n in raw certificate file violates the PEM certificate file format. Therefore if control characters are not accepted, maybe we'd better replace certRaw with cert64(Base64 encoded certificate, so that it's a single line without control characters), but this also needs changes from v2ray-plugin repository.

Mygod commented

Are you sure about that? I read RFC and I think it should be fine.

Here is the code related to certRaw from v2ray-plugin:

https://github.com/shadowsocks/v2ray-plugin/blob/59b8f4fc46c7be399dad0620121a89efa656dc9c/main.go#L185-L191

		} else if *cert != "" || *certRaw != "" {
			certificate := tls.Certificate{Usage: tls.Certificate_AUTHORITY_VERIFY}
			certificate.Certificate, err = readCertificate()
			if err != nil {
				return nil, newError("failed to read cert").Base(err)
			}
			tlsConfig.Certificate = []*tls.Certificate{&certificate}

v2ray-plugin is written in Go, and Go Language API needs the original format without removing \ns, check the examples here:

https://golang.org/pkg/crypto/tls/#example_X509KeyPair

image

Removing \n in the base64 content should be fine, but \n after the first line (-----BEGIN CERTIFICATE-----) and before the last line (-----END CERTIFICATE-----) should be kept, or you'll get tls: failed to find any PEM data in certificate input.

Is it possible to allow control characters? Or just escape them like replacing \n as \\n and replacing them back from the plugin? Or in worst adding \n after the first line and before the last line automatically inside the plugin?

BTW does certRaw option also work in v2ray-plugin CLI? If so, how does the CLI handle the new lines?

Okay, as @lixin9311 says in shadowsocks/v2ray-plugin#128, the PR should fix the bug in CLI. Seems v1.3.1 has included upstream changes, however it's still not working, server-side still get remote error: tls: bad certificate.

Also v1.3.1 doesn't include config GUI, only has a single config input.

Screenshot_20200225-112325

Mine does show the configuration dialogue properly, have you tried switching the system language to English? @ccloli

I submitted #22 to fix this issue.

And another PR is also necessary to fix this:

shadowsocks/v2ray-plugin#190

Could you help review and merge that fix?

@Mygod

@jonkerfan I downloaded the debug apk file of your PR from Circle CI, seems it works fine (the config UI is also working).

On server-side it says accepted instead of bad certificate, the connection test from Shadowsocks is succeed. I also tested some other apps, looks like it doesn't have any problem, the proxy connection is working.

What do you think ? @madeye

Apply for a certificate from Let’s Encrypt, then v2ray-plugin from F-droid works fine.

Detail:
#14 (comment)

@jonkerfan What version of android does plugin under? It doesn't work on android 10, seems ok when start but failed when I try to connect, the pc plugin works well.

@ODtian
Mine is Android 9

@jonkerfan Oh, it seems author 've already fix the problems which were mentioned in exist issues, in version 1.3.0, and I also try 1.3.1, but clearly none of them work on my device.
Is there any way to show the log of the plugin? so that I could find out what prevent me from connecting server.

Same issue here on Mi 9 with Android 11 at Nov. 2021. get "remote error: tls: bad certificate" on server side with this plugin. windows & linux client works good. The discussion last for 2 years. do you guys have plan to fix this? I currently have to use websocket+http to get v2ray-plugin work on android.

BTW, why do we have to pass a CA? it could be optional, in my case I definitely trust my server and it's cert, as I signed it with a root CA created by me. I also installed it into android's trust CA storage. still not work. it could be phone's bug. so could we make supplying a CA from client optional? In GFW Trojan android client, install the CA certificate also not working. but trojan client can simply turn off verifing server cert with client CA functionality. ss android v2ray plugin could do same to get rid of this issue.

Sounds like v2ray plugin were trying to use root CA as a authentication way from server side, as the error is come up from server side! This is no point. because root CA normally is public. it is only used to verify the server is trustworthy in practice history of CA certificate.

I encountered the same problem and found a workaround.
Of course, before this, you need to correctly configure the other settings of the Shadowsocks client for Android.

My server is self-registered using acme.sh. The public key file is generated as domain.cer. The content of domain.cer looks similar to the following:
-----BEGIN CERTIFICATE-----
public key
-----END CERTIFICATE-----
I copied the public key and paste it to config option "Certification for TLS verification" of v2ray plugin, then it works.

The issue only occurs when I hide my VPS IP using cloudflare and try to access the VPS directly by its IP address. So I guess this issue is caused by inconsistency between the IP address and the domain name.

it's a mess, I'm having the same issue.
But I'm not a web developer, so I can't be so sure about the solution as you may be.

For me I tried self-signed and zerossl certificates, they both failed , and I had the same bad certificate error, whic is so annoying.

workaround: copy content betwean -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- without newlines