diff --git a/android/org/gingercal/net/CertVerifier.java b/android/org/gingercal/net/CertVerifier.java index 40acfc7..28239c6 100644 --- a/android/org/gingercal/net/CertVerifier.java +++ b/android/org/gingercal/net/CertVerifier.java @@ -49,9 +49,11 @@ public final class CertVerifier { public static final class Untrusted extends IOException { public final String fingerprint, subject, issuer, expires; public final boolean changed; // a pin was set and this is a different certificate + public final List chain; // as the server sent it, leaf first - Untrusted(String fp, String subject, String issuer, String expires, boolean changed) { + Untrusted(String fp, String subject, String issuer, String expires, boolean changed, List chain) { super("untrusted certificate " + fp); + this.chain = chain; this.fingerprint = fp; this.subject = subject; this.issuer = issuer; @@ -93,7 +95,8 @@ public final class CertVerifier { } } - private static Untrusted untrusted(byte[] leafDer, boolean changed) { + private static Untrusted untrusted(List chainDer, boolean changed) { + byte[] leafDer = chainDer.get(0); String subject = "?", issuer = "?", expires = "?"; try { Certificate c = Certificate.getInstance(leafDer); @@ -101,7 +104,7 @@ public final class CertVerifier { issuer = String.valueOf(c.getIssuer()); expires = new java.text.SimpleDateFormat("yyyy-MM-dd", Locale.US).format(c.getEndDate().getDate()); } catch (Exception ignored) {} - return new Untrusted(sha256(leafDer), subject, issuer, expires, changed); + return new Untrusted(sha256(leafDer), subject, issuer, expires, changed, chainDer); } /** Lookup of trusted root certificates (DER) by the DER encoding of their subject name. */ @@ -130,7 +133,7 @@ public final class CertVerifier { if (p.length() > 0) { // a pinned server: exactly that certificate, whatever its name or issuer if (norm(sha256(chainDer.get(0))).equals(p)) return; - throw untrusted(chainDer.get(0), true); + throw untrusted(chainDer, true); } if (roots == null) throw new IOException("No trust store configured"); java.io.ByteArrayOutputStream all = new java.io.ByteArrayOutputStream(); @@ -149,6 +152,7 @@ public final class CertVerifier { } private static void verifyFully(List chainDer, String host) throws IOException { + final List chain0 = chainDer; List chain = new ArrayList(); for (int i = 0; i < chainDer.size(); i++) chain.add(Certificate.getInstance(chainDer.get(i))); @@ -171,9 +175,13 @@ public final class CertVerifier { // 1. Anchored directly by a trusted root? List anchors = roots.find(issuerDer); if (anchors != null) { + byte[] currentDer = current.getEncoded(ASN1Encoding.DER); for (int i = 0; i < anchors.size(); i++) { + // the very certificate the user chose to trust (e.g. self-signed) + if (java.util.Arrays.equals(anchors.get(i), currentDer)) return; Certificate root = Certificate.getInstance(anchors.get(i)); - if (signatureValid(current, root)) return; // trusted + // anything that signs must be a CA: a trusted site's own certificate can't vouch for another + if (isCa(root) && signatureValid(current, root)) return; // trusted } } @@ -186,7 +194,7 @@ public final class CertVerifier { if (!isCa(cand)) continue; if (signatureValid(current, cand)) { next = cand; break; } } - if (next == null) throw untrusted(chainDer.get(0), false); + if (next == null) throw untrusted(chain0, false); current = next; } throw new IOException("Certificate chain too long");