package org.gingercal; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.List; import java.util.UUID; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManager; import org.gingercal.Model.Cal; /** Talks to the legacy bridge the way Android 2.3 does: TLS 1.0, AES128-SHA, pinned cert. */ public class BridgeTest { static int fails; static void check(boolean ok, String what) { System.out.println((ok ? " ok " : " FAIL ") + what); if (!ok) fails++; } static class GingerbreadHttp implements Http { final Tls.PinTrust trust; final HttpClient c; GingerbreadHttp(String pin) throws Exception { trust = new Tls.PinTrust(pin); SSLContext ctx = SSLContext.getInstance("TLSv1"); ctx.init(null, new TrustManager[] {trust}, null); SSLParameters p = new SSLParameters(new String[] {"TLS_RSA_WITH_AES_128_CBC_SHA"}, new String[] {"TLSv1"}); c = HttpClient.newBuilder().sslContext(ctx).sslParameters(p).build(); } public Resp exec(String method, String url, String[] h, String body, String ct) throws IOException { HttpRequest.Builder b = HttpRequest.newBuilder(URI.create(url)).method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(body)); for (int i = 0; i + 1 < h.length; i += 2) b.header(h[i], h[i + 1]); if (ct != null) b.header("Content-Type", ct); trust.reset(); try { HttpResponse r = c.send(b.build(), HttpResponse.BodyHandlers.ofString()); Resp o = new Resp(); o.code = r.statusCode(); o.body = r.body(); o.etag = r.headers().firstValue("ETag").orElse(null); o.location = r.headers().firstValue("Location").orElse(null); return o; } catch (IOException e) { throw trust.translate(e); } catch (InterruptedException e) { throw new IOException(e); } } public void close() {} } public static void main(String[] a) throws Exception { String url = "https://127.0.0.1:8443"; String expected = a[0]; // 1. first contact: not trusted -> app shows fingerprint try { new Dav(new GingerbreadHttp(""), url, "xose", "secret").calendarHome(); check(false, "untrusted cert reported"); } catch (DavException.UntrustedCert u) { check(u.fingerprint.equals(expected) && !u.pinMismatch, "untrusted cert reported with fingerprint " + u.fingerprint.substring(0, 11) + "..."); } // 2. wrong pin -> mismatch warning try { new Dav(new GingerbreadHttp(expected.replace('1', '2')), url, "xose", "secret").calendarHome(); check(false, "pin mismatch reported"); } catch (DavException.UntrustedCert u) { check(u.pinMismatch, "pin mismatch reported"); } // 3. pinned: full flow through the bridge Dav dav = new Dav(new GingerbreadHttp(expected), url, "xose", "secret"); String home = dav.calendarHome(); check(home.equals(url + "/remote.php/dav/xose/"), "discovered home via bridge: " + home); List cals = dav.calendars(home); check(cals.size() == 2, "calendars via bridge: " + cals.size()); CoreTest.MemStore st = new CoreTest.MemStore(); Syncer.Result r = Syncer.sync(dav, st, System.currentTimeMillis(), 1, 60, false); check(r.events > 0, "sync via bridge: " + r.summary()); String uid = UUID.randomUUID().toString(); long s = ICal.startOfDay(System.currentTimeMillis()) + 2 * ICal.DAY + 9 * ICal.HOUR; Cal w = cals.get(0); dav.create(w.href, uid, ICal.newEvent(uid, "Via bridge", "", "", s, s + ICal.HOUR, false, 10)); String[] got = dav.get(w.href + uid + ".ics"); check(ICal.parse(got[0]).get(0).summary.equals("Via bridge"), "create+get via bridge"); dav.delete(w.href + uid + ".ics", got[1]); check(true, "delete via bridge"); System.out.println(fails == 0 ? "ALL PASSED" : fails + " FAILED"); System.exit(fails == 0 ? 0 : 1); } }