forked from I2P_Developers/i2p.i2p
News: Parse and store CRLs in news feed
This commit is contained in:
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@ public class NewsXMLParser {
|
|||||||
private final I2PAppContext _context;
|
private final I2PAppContext _context;
|
||||||
private final Log _log;
|
private final Log _log;
|
||||||
private List<NewsEntry> _entries;
|
private List<NewsEntry> _entries;
|
||||||
|
private List<CRLEntry> _crlEntries;
|
||||||
private NewsMetadata _metadata;
|
private NewsMetadata _metadata;
|
||||||
private XHTMLMode _mode;
|
private XHTMLMode _mode;
|
||||||
|
|
||||||
@ -145,11 +146,23 @@ public class NewsXMLParser {
|
|||||||
return _metadata;
|
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 {
|
private void extract(Node root) throws I2PParserException {
|
||||||
if (!root.getName().equals("feed"))
|
if (!root.getName().equals("feed"))
|
||||||
throw new I2PParserException("no feed in XML");
|
throw new I2PParserException("no feed in XML");
|
||||||
_metadata = extractNewsMetadata(root);
|
_metadata = extractNewsMetadata(root);
|
||||||
_entries = extractNewsEntries(root);
|
_entries = extractNewsEntries(root);
|
||||||
|
_crlEntries = extractCRLEntries(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static NewsMetadata extractNewsMetadata(Node feed) throws I2PParserException {
|
private static NewsMetadata extractNewsMetadata(Node feed) throws I2PParserException {
|
||||||
@ -252,7 +265,7 @@ public class NewsXMLParser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This does not check for any missing values.
|
* 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 {
|
private List<NewsEntry> extractNewsEntries(Node feed) throws I2PParserException {
|
||||||
List<NewsEntry> rv = new ArrayList<NewsEntry>();
|
List<NewsEntry> rv = new ArrayList<NewsEntry>();
|
||||||
@ -350,6 +363,40 @@ public class NewsXMLParser {
|
|||||||
return rv;
|
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
|
* Helper to get all Nodes matching the name
|
||||||
*
|
*
|
||||||
|
@ -23,9 +23,11 @@ import java.util.StringTokenizer;
|
|||||||
import net.i2p.app.ClientAppManager;
|
import net.i2p.app.ClientAppManager;
|
||||||
import net.i2p.crypto.SU3File;
|
import net.i2p.crypto.SU3File;
|
||||||
import net.i2p.crypto.TrustedUpdate;
|
import net.i2p.crypto.TrustedUpdate;
|
||||||
|
import net.i2p.data.Base64;
|
||||||
import net.i2p.data.DataHelper;
|
import net.i2p.data.DataHelper;
|
||||||
import net.i2p.router.RouterContext;
|
import net.i2p.router.RouterContext;
|
||||||
import net.i2p.router.RouterVersion;
|
import net.i2p.router.RouterVersion;
|
||||||
|
import net.i2p.router.news.CRLEntry;
|
||||||
import net.i2p.router.news.NewsEntry;
|
import net.i2p.router.news.NewsEntry;
|
||||||
import net.i2p.router.news.NewsManager;
|
import net.i2p.router.news.NewsManager;
|
||||||
import net.i2p.router.news.NewsMetadata;
|
import net.i2p.router.news.NewsMetadata;
|
||||||
@ -41,6 +43,7 @@ import net.i2p.util.FileUtil;
|
|||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.PortMapper;
|
import net.i2p.util.PortMapper;
|
||||||
import net.i2p.util.ReusableGZIPInputStream;
|
import net.i2p.util.ReusableGZIPInputStream;
|
||||||
|
import net.i2p.util.SecureFile;
|
||||||
import net.i2p.util.SecureFileOutputStream;
|
import net.i2p.util.SecureFileOutputStream;
|
||||||
import net.i2p.util.SSLEepGet;
|
import net.i2p.util.SSLEepGet;
|
||||||
import net.i2p.util.SystemVersion;
|
import net.i2p.util.SystemVersion;
|
||||||
@ -509,6 +512,12 @@ class NewsFetcher extends UpdateRunner {
|
|||||||
nmgr.storeEntries(nodes);
|
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
|
// store entries and metadata in old news.xml format
|
||||||
String sudVersion = su3.getVersionString();
|
String sudVersion = su3.getVersionString();
|
||||||
String signingKeyName = su3.getSignerString();
|
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.
|
* Output in the old format.
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user