News: Parse and store CRLs in news feed

This commit is contained in:
zzz
2016-04-28 21:55:17 +00:00
parent 03adda3bde
commit 52c9bf6d80
3 changed files with 135 additions and 1 deletions

View File

@ -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;
}
}

View File

@ -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
*

View File

@ -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.
*