diff --git a/net/AssetRoots.java b/net/AssetRoots.java index 2b8d0d2..b8d4df9 100644 --- a/net/AssetRoots.java +++ b/net/AssetRoots.java @@ -4,35 +4,47 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import android.content.Context; -/** Mozilla roots shipped as assets/roots/-.der */ +/** + * Mozilla roots shipped as assets/roots/-.der + * + * Roots are read from the APK once and kept: an app installed on the SD card loses its APK while the + * card is shared over USB, and a failed read must not leave every server "untrusted" until a restart. + */ public final class AssetRoots implements CertVerifier.RootStore { private final Context ctx; - private Set names; + private Set names; // asset file names, once listed + private final Map> found = new HashMap>(); // by subject hash; null = none public AssetRoots(Context c) { ctx = c.getApplicationContext(); } public synchronized List find(byte[] subjectDer) { + String h = CertVerifier.sha1Hex(subjectDer); + if (found.containsKey(h)) return found.get(h); try { if (names == null) { - names = new HashSet(); String[] l = ctx.getAssets().list("roots"); - if (l != null) for (int i = 0; i < l.length; i++) names.add(l[i]); + if (l == null || l.length == 0) return null; // APK unreadable right now: try again next time + Set n = new HashSet(); + for (int i = 0; i < l.length; i++) n.add(l[i]); + names = n; } - String h = CertVerifier.sha1Hex(subjectDer); List out = null; for (int i = 0; names.contains(h + "-" + i + ".der"); i++) { if (out == null) out = new ArrayList(); out.add(readAll(ctx.getAssets().open("roots/" + h + "-" + i + ".der"))); } + found.put(h, out); return out; } catch (IOException e) { - return null; + return null; // not cached: the next handshake reads again } }