android
android copied to clipboard
Add client certificate support (https://github.com/gotify/android/issues/229)
So a few things to point out:
-
The
@RequiresApi(api = Build.VERSION_CODES.O)annotations were added because Android studio complained about using some of the base64 classes. Everything appeared to run without them, but I'm not sure if it's best practice to not have them. -
The 3 client cert settings added were to keep the advanced settings dialog in line with how it currently behaves - there's no way to guarantee being able to show a name based on the DN of a self-created cert, so I show the file name of the actual picked cert file.
-
CertUtils.java:104 will catch the correct exception when the password for the cert is incorrect although it is hacky having to check the exception text (not sure why it's such a generic exception type). With that said, the current way the exception handling is routing and the snackbar messages are constructed, this message will never be shown to the user. They will get another message about an error 0, which I believe the SSL connection failing to handshake.
Ideally, with a refactor of the advanced settings screen, the user could test the client cert password before committing the login information permanently to settings.
Let me know what you think and if you want anything tweaked.
Should we try to implement this again?
As the codebase has changed a lot (especially because of the transition to Kotlin), it would be the best idea to start from scratch and only take inspiration from this PR.
Yeah, this feature is useful, it was also requested in a reddit thread somewhere.
I've now worked a bit on this.
The Android client side should be mostly implemented, but I'm really struggling with setting up the reverse proxy (Caddy) with Gotify.
My current setup responds with 308, according to curl with what seems a redirection loop:
oci-main.cloud.cyb3rko:2015 {
tls internal
reverse_proxy localhost:80
}
where gotify runs on port 80.
* Connection #0 to host oci-main.cloud.cyb3rko left intact
* Issue another request to this URL: 'https://oci-main.cloud.cyb3rko:2015/'
...
* Using Stream ID: 3 (easy handle 0x5d9238dbdeb0)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: oci-main.cloud.cyb3rko:2015
> user-agent: curl/7.81.0
> accept: */*
>
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 308
< date: Wed, 17 Apr 2024 13:49:57 GMT
< location: https://oci-main.cloud.cyb3rko:2015/
< server: Caddy
< server: Caddy
< content-length: 0
<
* Connection #0 to host oci-main.cloud.cyb3rko left intact
* Issue another request to this URL: 'https://oci-main.cloud.cyb3rko:2015/'
...
* Using Stream ID: 5 (easy handle 0x5d9238dbdeb0)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET / HTTP/2
> Host: oci-main.cloud.cyb3rko:2015
> user-agent: curl/7.81.0
> accept: */*
I do not have any experience with reverse proxies, any ideas?
Are you accessing the https port? The doubled server header
< server: Caddy
< server: Caddy
seems suspicious, is the request proxied twice? You can try enable debug logging and look what caddy is saying.
$ cat Caddyfile
{
http_port 8000
https_port 8443
log {
level DEBUG
}
}
dyne.local:2015 {
reverse_proxy localhost:8080
tls internal
}
$ curl -k https://dyne.local:2015/health -v
* Host dyne.local:2015 was resolved.
* IPv6: (none)
* IPv4: 192.168.178.2
* Trying 192.168.178.2:2015...
* Connected to dyne.local (192.168.178.2) port 2015
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / x25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
* subject: [NONE]
* start date: Apr 17 18:17:59 2024 GMT
* expire date: Apr 18 06:17:59 2024 GMT
* issuer: CN=Caddy Local Authority - ECC Intermediate
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* Certificate level 1: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using ecdsa-with-SHA256
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://dyne.local:2015/health
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: dyne.local:2015]
* [HTTP/2] [1] [:path: /health]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
> GET /health HTTP/2
> Host: dyne.local:2015
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/2 200
< alt-svc: h3=":2015"; ma=2592000
< content-type: application/json; charset=utf-8
< date: Wed, 17 Apr 2024 18:19:29 GMT
< server: Caddy
< content-length: 37
<
* Connection #0 to host dyne.local left intact
{"health":"green","database":"green"}%
Nevermind, I got it working (incl. client side certificate authentication).
I've found some issues in the implementation, I will try to figure it out and continue in a new PR.