diff --git a/apps/routerconsole/java/src/net/i2p/router/news/CRLEntry.java b/apps/routerconsole/java/src/net/i2p/router/news/CRLEntry.java
new file mode 100644
index 0000000000000000000000000000000000000000..8b8f53935992bacfc372bd062afae7e2fb2839cb
--- /dev/null
+++ b/apps/routerconsole/java/src/net/i2p/router/news/CRLEntry.java
@@ -0,0 +1,32 @@
+package net.i2p.router.news;
+
+import net.i2p.data.DataHelper;
+
+/**
+ *  One CRL.
+ *  Any String fields may be null.
+ *
+ *  @since 0.9.26
+ */
+public class CRLEntry {
+    public String data;
+    public String id;
+    public long updated;
+
+    @Override
+    public boolean equals(Object o) {
+        if(o == null)
+            return false;
+        if(!(o instanceof CRLEntry))
+            return false;
+        CRLEntry e = (CRLEntry) o;
+        return updated == e.updated &&
+               DataHelper.eq(id, e.id) &&
+               DataHelper.eq(data, e.data);
+    }
+    
+    @Override
+    public int hashCode() {
+        return (int) updated;
+    }
+}
diff --git a/apps/routerconsole/java/src/net/i2p/router/news/NewsXMLParser.java b/apps/routerconsole/java/src/net/i2p/router/news/NewsXMLParser.java
index 5f35fa8c6dd62719376a17c0334bc62e4d92f528..534acb3053feb6d1a18a482ac7a8efc7a972278f 100644
--- a/apps/routerconsole/java/src/net/i2p/router/news/NewsXMLParser.java
+++ b/apps/routerconsole/java/src/net/i2p/router/news/NewsXMLParser.java
@@ -32,6 +32,7 @@ public class NewsXMLParser {
     private final I2PAppContext _context;
     private final Log _log;
     private List<NewsEntry> _entries;
+    private List<CRLEntry> _crlEntries;
     private NewsMetadata _metadata;
     private XHTMLMode _mode;
 
@@ -145,11 +146,23 @@ public class NewsXMLParser {
         return _metadata;
     }
 
+    /**
+     *  The news CRL entries.
+     *  Must call parse() first.
+     *
+     *  @return unsorted, null if none
+     *  @since 0.9.26
+     */
+    public List<CRLEntry> getCRLEntries() {
+        return _crlEntries;
+    }
+
     private void extract(Node root) throws I2PParserException {
         if (!root.getName().equals("feed"))
             throw new I2PParserException("no feed in XML");
         _metadata = extractNewsMetadata(root);
         _entries = extractNewsEntries(root);
+        _crlEntries = extractCRLEntries(root);
     }
 
     private static NewsMetadata extractNewsMetadata(Node feed) throws I2PParserException {
@@ -252,7 +265,7 @@ public class NewsXMLParser {
 
     /**
      *  This does not check for any missing values.
-     *  Any fields in any NewsEntry may be null.
+     *  Any field in any NewsEntry may be null.
      */
     private List<NewsEntry> extractNewsEntries(Node feed) throws I2PParserException {
         List<NewsEntry> rv = new ArrayList<NewsEntry>();
@@ -350,6 +363,40 @@ public class NewsXMLParser {
         return rv;
     }
 
+    /**
+     *  This does not check for any missing values.
+     *  Any field in any CRLEntry may be null.
+     *
+     *  @return null if none
+     *  @since 0.9.26
+     */
+    private List<CRLEntry> extractCRLEntries(Node feed) throws I2PParserException {
+        Node rev = feed.getNode("i2p:revocations");
+        if (rev == null)
+            return null;
+        List<Node> entries = getNodes(rev, "i2p:crl");
+        if (entries.isEmpty())
+            return null;
+        List<CRLEntry> rv = new ArrayList<CRLEntry>(entries.size());
+        for (Node entry : entries) {
+            CRLEntry e = new CRLEntry();
+            String a = entry.getAttributeValue("id");
+            if (a.length() > 0)
+                e.id = a;
+            a = entry.getAttributeValue("updated");
+            if (a.length() > 0) {
+                long time = RFC3339Date.parse3339Date(a.trim());
+                if (time > 0)
+                    e.updated = time;
+            }
+            a = entry.getValue();
+            if (a != null)
+                e.data = a.trim();
+            rv.add(e);
+        }
+        return rv;
+    }
+
     /**
      *  Helper to get all Nodes matching the name
      *
diff --git a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
index f57d6d7c0dd24c6927266c2886c889cf551e9e01..39b42d105bcb0bac86390ca9e2c2c6e9caa6007b 100644
--- a/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
+++ b/apps/routerconsole/java/src/net/i2p/router/update/NewsFetcher.java
@@ -23,9 +23,11 @@ import java.util.StringTokenizer;
 import net.i2p.app.ClientAppManager;
 import net.i2p.crypto.SU3File;
 import net.i2p.crypto.TrustedUpdate;
+import net.i2p.data.Base64;
 import net.i2p.data.DataHelper;
 import net.i2p.router.RouterContext;
 import net.i2p.router.RouterVersion;
+import net.i2p.router.news.CRLEntry;
 import net.i2p.router.news.NewsEntry;
 import net.i2p.router.news.NewsManager;
 import net.i2p.router.news.NewsMetadata;
@@ -41,6 +43,7 @@ import net.i2p.util.FileUtil;
 import net.i2p.util.Log;
 import net.i2p.util.PortMapper;
 import net.i2p.util.ReusableGZIPInputStream;
+import net.i2p.util.SecureFile;
 import net.i2p.util.SecureFileOutputStream;
 import net.i2p.util.SSLEepGet;
 import net.i2p.util.SystemVersion;
@@ -509,6 +512,12 @@ class NewsFetcher extends UpdateRunner {
                     nmgr.storeEntries(nodes);
                 }
             }
+            // Persist any new CRL entries
+            List<CRLEntry> crlEntries = parser.getCRLEntries();
+            if (crlEntries != null)
+                persistCRLEntries(crlEntries);
+            else
+                _log.info("No CRL entries found in news feed");
             // store entries and metadata in old news.xml format
             String sudVersion = su3.getVersionString();
             String signingKeyName = su3.getSignerString();
@@ -544,6 +553,52 @@ class NewsFetcher extends UpdateRunner {
         }
     }
 
+    /**
+     *  Output any updated CRL entries
+     *
+     *  @since 0.9.26
+     */
+    private void persistCRLEntries(List<CRLEntry> entries) {
+        File dir = new SecureFile(_context.getConfigDir(), "certificates");
+        if (!dir.exists() && !dir.mkdir()) {
+            _log.error("Failed to create CRL directory " + dir);
+            return;
+        }
+        dir = new SecureFile(dir, "revocations");
+        if (!dir.exists() && !dir.mkdir()) {
+            _log.error("Failed to create CRL directory " + dir);
+            return;
+        }
+        int i = 0;
+        for (CRLEntry e : entries) {
+            if (e.id == null || e.data == null) {
+                if (_log.shouldWarn())
+                    _log.warn("Bad CRL entry received");
+                continue;
+            }
+            byte[] bid = DataHelper.getUTF8(e.id);
+            byte[] hash = new byte[32];
+            _context.sha().calculateHash(bid, 0, bid.length, hash, 0);
+            String name = "crl-" + Base64.encode(hash) + ".crl";
+            File f = new File(dir, name);
+            if (f.exists() && f.lastModified() >= e.updated)
+                continue;
+            OutputStream out = null;
+            try {
+                out = new SecureFileOutputStream(f);
+                out.write(DataHelper.getUTF8(e.data));
+            } catch (IOException ioe) {
+                _log.error("Failed to write CRL", ioe);
+            } finally {
+                if (out != null) try { out.close(); } catch (IOException ioe) {}
+            }
+            f.setLastModified(e.updated);
+            i++;
+        }
+        if (i > 0)
+            _log.logAlways(Log.WARN, "Stored " + i + " new CRL " + (i > 1 ? "entries" : "entry"));
+    }
+
     /**
      *  Output in the old format.
      *