Import GingerCal 1.0 source
This commit is contained in:
commit
01f49852ab
116 files changed
+5079
No files matched your search
@@ -0,0 +1,147 @@
|
||||
# GingerCal — Nextcloud CalDAV for Android 2.3 (Galaxy Mini)
|
||||
|
||||
A small native calendar client for Gingerbread (API 9/10). Built for a Samsung Galaxy Mini
|
||||
(GT-S5570, 240×320, Android 2.3.5) talking to Nextcloud, reached through Pangolin.
|
||||
|
||||
**What it does**
|
||||
|
||||
- Agenda of upcoming events grouped by day, colour-coded per calendar, cached offline (SQLite)
|
||||
- Create, edit and delete events (title, all-day, start/end, location, notes, alarm)
|
||||
- Repeating events are shown correctly (server-side expansion, with a client-side fallback);
|
||||
they can be deleted on the phone but edited only in Nextcloud
|
||||
- Pick which calendars to show and which one new events go to
|
||||
- Background sync every 1–24 h, plus phone notifications N minutes before timed events
|
||||
- Certificate pinning, so a self-signed "legacy" endpoint is still safe against interception
|
||||
|
||||
It does not feed the stock Samsung calendar: 2.3 has no public calendar-provider API for that.
|
||||
|
||||
## Why HTTPS breaks on 2.3, and the fix
|
||||
|
||||
Three separate problems, all fatal for a normal Pangolin/Traefik URL:
|
||||
|
||||
| Problem | Gingerbread | Pangolin (Traefik) |
|
||||
|---|---|---|
|
||||
| TLS version | TLS 1.0 max | TLS 1.2 minimum |
|
||||
| Root CA | no ISRG Root X1 (Let's Encrypt) | Let's Encrypt certificates |
|
||||
| SNI | unreliable in Apache HttpClient | routes HTTPS by SNI |
|
||||
|
||||
Lowering Traefik's TLS settings would weaken every site you host and still leave the CA problem.
|
||||
Instead, put a tiny **legacy-TLS bridge** next to Nextcloud and expose it with a Pangolin
|
||||
**raw TCP resource** (port-based, so no SNI needed). The bridge speaks TLS 1.0 with a
|
||||
self-signed certificate that the app pins by fingerprint, and forwards **only** `/remote.php/dav/`
|
||||
to Nextcloud.
|
||||
|
||||
```
|
||||
Galaxy Mini --TLS 1.0, pinned cert--> Pangolin VPS :8443 (raw TCP, WireGuard tunnel)
|
||||
--> newt --> caldav-bridge (nginx) --HTTP--> Nextcloud (CalDAV only)
|
||||
```
|
||||
|
||||
### Option A: bridge through Pangolin (works anywhere)
|
||||
|
||||
1. **Bridge** (on the Nextcloud host, in `bridge/`):
|
||||
- Edit `nginx.conf`: the `server nextcloud:80;` upstream and `set $nc_host` (a name in
|
||||
Nextcloud's `trusted_domains`). If Nextcloud forces HTTPS, also switch to `proxy_pass https://`
|
||||
(see "Forced HTTPS / HSTS" below).
|
||||
- Edit `docker-compose.yml`: the Docker network your Nextcloud container is on.
|
||||
- `./make-cert.sh` creates the certificate and prints its SHA-256 fingerprint. Note it.
|
||||
- `docker compose up -d`
|
||||
2. **Pangolin server** (VPS):
|
||||
- `config/config.yml`: `flags: allow_raw_resources: true`
|
||||
- `docker-compose.yml`, `gerbil` service: add port `8443:8443`
|
||||
- `config/traefik/traefik_config.yml`, `entryPoints`: add
|
||||
`tcp-8443: { address: ":8443/tcp" }` (the `tcp-<port>` name is required)
|
||||
- Open TCP 8443 in the VPS firewall, then `docker compose down && docker compose up -d`
|
||||
3. **Pangolin dashboard**: Resources, Add Resource, **Raw TCP/UDP**, TCP, public port **8443**,
|
||||
target = the bridge as seen from your newt site (`caldav-bridge:8443` if newt shares its Docker
|
||||
network, otherwise `127.0.0.1:8443` with the port published). Leave Proxy Protocol off.
|
||||
4. **Phone**: server address `https://<your-pangolin-domain-or-IP>:8443`. On the first test
|
||||
the app shows the certificate fingerprint; tap **Trust** only if it matches step 1.
|
||||
|
||||
Pangolin's SSO does not apply to raw TCP resources; protection here is Nextcloud's login (use an
|
||||
app password) plus the pinned certificate. The web UI isn't reachable through the bridge.
|
||||
|
||||
Optional, to let Nextcloud's brute-force protection see real client IPs: tick **Enable Proxy
|
||||
Protocol** on the resource, add the `pp-transport-v1/v2` block to Pangolin's
|
||||
`dynamic_config.yml` (see the Pangolin raw-resources docs), and in `nginx.conf` use
|
||||
`listen 8443 ssl proxy_protocol;`, `set_real_ip_from <newt address>;`,
|
||||
`real_ip_header proxy_protocol;`. Enable both sides together or every connection fails.
|
||||
|
||||
### Option B: home Wi-Fi only (no Pangolin)
|
||||
|
||||
Use `http://<nextcloud-LAN-IP>` as the server address. Nothing to set up, but it syncs only at
|
||||
home and the password crosses your LAN unencrypted. The app warns if `http://` points at a
|
||||
non-LAN address.
|
||||
|
||||
Avoid a plain-HTTP Pangolin resource: it would send your password in clear over the internet.
|
||||
|
||||
### Forced HTTPS / HSTS
|
||||
|
||||
**HSTS itself doesn't matter to the app.** `Strict-Transport-Security` is enforced by browsers;
|
||||
the app's HTTP client ignores it, and a Pangolin raw TCP resource passes bytes through without
|
||||
adding headers. The bridge strips the header anyway, so a modern browser that you point at
|
||||
the bridge (e.g. while debugging) doesn't lock itself to the self-signed certificate.
|
||||
|
||||
**What does matter is a Nextcloud that redirects plain HTTP to its public `https://` URL**
|
||||
(web server rule, `overwriteprotocol`, `overwrite.cli.url`). Symptoms in the app:
|
||||
|
||||
| App says | Cause | Fix |
|
||||
|---|---|---|
|
||||
| "Redirect loop … switch the bridge to proxy_pass https://" | bridge talks HTTP to a Nextcloud that redirects HTTP→HTTPS | in `nginx.conf`: `server nextcloud:443;` and `proxy_pass https://nextcloud;` |
|
||||
| "The server redirected http:// to https://… (forces HTTPS / HSTS)" | Option B: phone talks `http://` to that Nextcloud on the LAN | use the bridge address, or let the LAN vhost serve `/remote.php/dav/` without the redirect |
|
||||
| "The server redirected to https://cloud…, not the address set in the app" | an absolute redirect wasn't rewritten | keep the `proxy_redirect` and `absolute_redirect off` lines in `nginx.conf` |
|
||||
|
||||
With `proxy_pass https://` only the bridge-to-Nextcloud hop uses HTTPS (modern TLS, any
|
||||
certificate, since it's your own server). The phone side stays TLS 1.0 with the pinned
|
||||
certificate. If the bridge can't reach Nextcloud directly, `proxy_pass` can also point at the
|
||||
public Pangolin URL. In that case Pangolin authentication must let `/remote.php/dav/` through, and
|
||||
you should turn `proxy_ssl_verify` on.
|
||||
|
||||
## Install on the phone
|
||||
|
||||
1. Settings, Applications, tick **Unknown sources**.
|
||||
2. Copy `GingerCal.apk` to the SD card (USB) and open it with the file manager, or download it
|
||||
over plain `http://` (the 2.3 browser can't open most HTTPS sites either).
|
||||
3. Open GingerCal, Menu, Settings. In Nextcloud create an app password: Settings, Security,
|
||||
Devices & sessions. Fill in server, user, app password, then **Save and test connection**.
|
||||
|
||||
Menu key: Sync, New event, Calendars, Settings. Tap an event for details, Edit and Delete.
|
||||
|
||||
## Building
|
||||
|
||||
Needs JDK 11+, Python 3 with Pillow, and `d8` (Android SDK build-tools). No Gradle, no aapt:
|
||||
`tools/mkapk.py` writes the binary manifest and resource table itself.
|
||||
|
||||
```sh
|
||||
./build.sh # uses ./stubs to compile
|
||||
ANDROID_JAR=$SDK/platforms/android-10/android.jar ./build.sh # or the real platform jar
|
||||
```
|
||||
|
||||
`gingercal.keystore` (password `android`) is the key the supplied APK was signed with; keep
|
||||
building with it so updates install over the existing app. A different key means uninstalling
|
||||
first. Signing uses SHA-1 because Android before 4.3 rejects anything else.
|
||||
|
||||
Tests (desktop JVM, need a CalDAV server such as Radicale on :5232 with user `xose`/`secret`):
|
||||
|
||||
```sh
|
||||
javac -d out src/org/gingercal/*.java test/org/gingercal/*.java
|
||||
java -cp out org.gingercal.CoreTest # discovery, sync, recurrence, create/edit/delete
|
||||
java -cp out org.gingercal.RedirectTest # forced-HTTPS / redirect-loop handling (no server needed)
|
||||
```
|
||||
|
||||
`tools/check_api.py` verifies every framework call in the dex against published Android API
|
||||
declarations.
|
||||
|
||||
## Layout
|
||||
|
||||
- `src/` CalDAV core, platform-neutral: discovery, PROPFIND/REPORT, iCalendar, sync, pinning
|
||||
- `android/` activities, sync service, alarms, SQLite cache, Apache HttpClient transport
|
||||
- `stubs/` compile-only Android API 10 signatures (never packaged)
|
||||
- `bridge/` nginx legacy-TLS bridge: config, compose file, certificate script
|
||||
- `tools/` APK packer, icon, zipalign, d8 workaround, API checker
|
||||
|
||||
## Security notes
|
||||
|
||||
TLS 1.0 with AES-CBC is old but, combined with a pinned certificate, still keeps the connection
|
||||
private and authenticated against anyone without the bridge's private key. The bridge exposes
|
||||
only the DAV endpoint. Use an app password so you can revoke the phone from Nextcloud. If the
|
||||
phone ever reports that the certificate changed and you didn't regenerate it, don't trust it.
|
||||
Reference in new issue
Block a user