package org.gingercal.net; 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 * * 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; // 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) { String[] l = ctx.getAssets().list("roots"); 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; } 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; // not cached: the next handshake reads again } } static byte[] readAll(InputStream in) throws IOException { try { ByteArrayOutputStream b = new ByteArrayOutputStream(); byte[] buf = new byte[4096]; int n; while ((n = in.read(buf)) > 0) b.write(buf, 0, n); return b.toByteArray(); } finally { in.close(); } } }