forked from I2P_Developers/i2p.i2p
to branch 'i2p.i2p' (head a1c2ba4663abc7470f427c6a14854707d58b486a)
Prop from branch i2p.i2p.zzz.ecdsa:
* Build:
- Generate su3 file in release target
- Add zzz's new RSA 4096 pubkey cert for updates
- Fix checkcerts.sh
* Console: Move advanced setting to HelperBase
* DSAEngine changes:
- Implement raw sign/verify for other SigTypes
- Add sign/verify methods using Java keys
* ECDSA Support:
- Add ECConstants which looks for named curves and falls back to
explicitly defining the curves
- Add support for ECDSA to SigType, DSAEngine and KeyGenerator
- Attempt to add BC as a Provider
- genSpec: fallback to BC provider
* EepGet:
- Fix non-proxied PartialEepGet
- Prevent non-proxied eepget for an I2P host
* KeyGenerator changes:
- Generate key pairs for all supported SigTypes
- KeyPairGen: Catch ProviderException, fallback to BC provider
- Add KeyGenerator main() tests
* KeyRing and DirKeyRing added: simple backend for storing X.509 certs
* KeyStoreUtil added:
- Consolidate KeyStore code from SSLEepGet, I2CPSSLSocketFactory,
SSLClientListenerRunner, and RouterConsoleRunner into new
KeyStoreUtil and CertUtil classes in net.i2p.crypto (ticket #744)
- Change default to RSA 2048 (ticket #1017)
- Set file modes on written keys
- Overwrite check in createKeys()
- New getCert(), getKey()
- Extend keygen max wait
- Read back private key to verify after keygen
- Validate cert after reading from file
- Validate CN in cert
- Specify cert signature algorithm when generating keys
* NativeBigInteger: Tweak to prevent early context instantiation
* RSA support added: constants, parameters, sig types, support in DSAEngine, KeyGenerator, SigUtil
* SHA1Hash: Add no-arg constructor
* SigType changes:
- Add parameters (curve specs) to SigTypes
- Add getHashInstance()
- Add RSA, fix ECDSA
- Renumber, rename, comment out types that are too short.
* SigUtil added:
- Converters from Java formats (ASN.1, X.509, PKCS#8)
to I2P formats for Signatures and SigningKeys
- Move ASN.1 converter from DSAEngine to SigUtil, generalize
for variable length, add support for longer sequences,
add more sanity checks, add more exceptions
- Move I2P-to-Java DSA key conversion from DSAEngine to SigUtil
- Add Java-to-I2P DSA key conversion
- Add Java key import
- New split() and combine() methods
* SSLEepGet: Move all certificates to certificates/ssl, in preparation
for other certificate uses by SU3File
* SU3File changes:
- Support all SigTypes
- Implement keygen
- Readahead to get sigtype on verify, as we need the hash type
- Enum for content type
- Add unknown content type, make default
- Fix NPE if private key not found or sign fails
- Store generated keys in keystore, and get private key from keystore
for signing, in Java format
- Use Java keys to sign and verify so we don't
lose the key parameters in the conversion to I2P keys
- Type checking of Java private key vs. type when signing
- Use certs instead of public keys for verification
- Fix arg processing
- Improve validate-without-extract
- New extract command
- Change static fields to avoid early context init
- Reduce PRNG buffer size for faster signing
* Update: Preliminary work for su3 router updates:
- New ROUTER_SIGNED_SU3 UpdateType
- Add support for torrent and HTTP
- Refactor UpdateRunners to return actual UpdateType
- Deal with signed/su3 conflicts
- Verify and extract su3 files.
- Stub out support for clearnet su3 updating
- New config for proxying news, separate from proxying update
- PartialEepGet and SSLEepGet tweaks to support clearnet update
- Remove proxy, key, and url config from /configupdate
- More URI checks in UpdateRunner
- Add https support for news fetch
- Add su3 mime type
- Reset found version in update loop so we don't fetch from
the next host too.
- Prevent NPE on version after SSL fetch
311 lines
9.9 KiB
Java
311 lines
9.9 KiB
Java
package org.klomp.snark;
|
|
|
|
import java.io.File;
|
|
import java.net.URI;
|
|
import java.util.List;
|
|
|
|
import net.i2p.I2PAppContext;
|
|
import net.i2p.crypto.TrustedUpdate;
|
|
import net.i2p.data.DataHelper;
|
|
import net.i2p.update.*;
|
|
import net.i2p.util.Log;
|
|
import net.i2p.util.SimpleTimer2;
|
|
import net.i2p.util.VersionComparator;
|
|
|
|
/**
|
|
* The downloader for router signed updates.
|
|
*
|
|
* @since 0.9.4
|
|
*/
|
|
class UpdateRunner implements UpdateTask, CompleteListener {
|
|
private final I2PAppContext _context;
|
|
private final Log _log;
|
|
private final UpdateManager _umgr;
|
|
private final SnarkManager _smgr;
|
|
private final UpdateType _type;
|
|
private final List<URI> _urls;
|
|
private volatile boolean _isRunning;
|
|
private volatile boolean _hasMetaInfo;
|
|
private volatile boolean _isComplete;
|
|
private final String _newVersion;
|
|
private URI _currentURI;
|
|
private Snark _snark;
|
|
|
|
private static final long MAX_LENGTH = 30*1024*1024;
|
|
private static final long METAINFO_TIMEOUT = 30*60*1000;
|
|
private static final long COMPLETE_TIMEOUT = 3*60*60*1000;
|
|
private static final long CHECK_INTERVAL = 3*60*1000;
|
|
|
|
public UpdateRunner(I2PAppContext ctx, UpdateManager umgr, SnarkManager smgr,
|
|
UpdateType type, List<URI> uris, String newVersion) {
|
|
_context = ctx;
|
|
_log = ctx.logManager().getLog(getClass());
|
|
_umgr = umgr;
|
|
_smgr = smgr;
|
|
_type = type;
|
|
_urls = uris;
|
|
_newVersion = newVersion;
|
|
}
|
|
|
|
//////// begin UpdateTask methods
|
|
|
|
public boolean isRunning() { return _isRunning; }
|
|
|
|
public void shutdown() {
|
|
_isRunning = false;
|
|
if (_snark != null) {
|
|
|
|
}
|
|
}
|
|
|
|
public UpdateType getType() { return _type; }
|
|
|
|
public UpdateMethod getMethod() { return UpdateMethod.TORRENT; }
|
|
|
|
public URI getURI() { return _currentURI; }
|
|
|
|
public String getID() { return ""; }
|
|
|
|
//////// end UpdateTask methods
|
|
|
|
public void start() {
|
|
_isRunning = true;
|
|
update();
|
|
}
|
|
|
|
/**
|
|
* Loop through the entire list of update URLs.
|
|
* For each one, first get the version from the first 56 bytes and see if
|
|
* it is newer than what we are running now.
|
|
* If it is, get the whole thing.
|
|
*/
|
|
private void update() {
|
|
for (URI uri : _urls) {
|
|
_currentURI = uri;
|
|
String updateURL = uri.toString();
|
|
try {
|
|
MagnetURI magnet = new MagnetURI(_smgr.util(), updateURL);
|
|
byte[] ih = magnet.getInfoHash();
|
|
// do we already have it?
|
|
_snark = _smgr.getTorrentByInfoHash(ih);
|
|
if (_snark != null) {
|
|
if (_snark.getMetaInfo() != null) {
|
|
_hasMetaInfo = true;
|
|
Storage storage = _snark.getStorage();
|
|
if (storage != null && storage.complete())
|
|
processComplete(_snark);
|
|
}
|
|
if (!_isComplete) {
|
|
if (_snark.isStopped() && !_snark.isStarting())
|
|
_snark.startTorrent();
|
|
// we aren't a listener so we must poll
|
|
new Watcher();
|
|
}
|
|
break;
|
|
}
|
|
String name = magnet.getName();
|
|
String trackerURL = magnet.getTrackerURL();
|
|
if (trackerURL == null && !_smgr.util().shouldUseDHT() &&
|
|
!_smgr.util().shouldUseOpenTrackers()) {
|
|
// but won't we use OT as a failsafe even if disabled?
|
|
_umgr.notifyAttemptFailed(this, "No tracker, no DHT, no OT", null);
|
|
continue;
|
|
}
|
|
_snark = _smgr.addMagnet(name, ih, trackerURL, true, true, this);
|
|
if (_snark != null) {
|
|
updateStatus("<b>" + _smgr.util().getString("Updating from {0}", linkify(updateURL)) + "</b>");
|
|
new Timeout();
|
|
break;
|
|
}
|
|
} catch (IllegalArgumentException iae) {
|
|
_log.error("Invalid update URL", iae);
|
|
}
|
|
}
|
|
if (_snark == null)
|
|
fatal("No valid URLs");
|
|
}
|
|
|
|
/**
|
|
* This will run twice, once at the metainfo timeout and
|
|
* once at the complete timeout.
|
|
*/
|
|
private class Timeout extends SimpleTimer2.TimedEvent {
|
|
private final long _start = _context.clock().now();
|
|
|
|
public Timeout() {
|
|
super(_context.simpleTimer2(), METAINFO_TIMEOUT);
|
|
}
|
|
|
|
public void timeReached() {
|
|
if (_isComplete || !_isRunning)
|
|
return;
|
|
if (!_hasMetaInfo) {
|
|
fatal("Metainfo timeout");
|
|
return;
|
|
}
|
|
if (_context.clock().now() - _start >= COMPLETE_TIMEOUT) {
|
|
fatal("Complete timeout");
|
|
return;
|
|
}
|
|
reschedule(COMPLETE_TIMEOUT - METAINFO_TIMEOUT);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Rarely used - only if the user added the torrent, so
|
|
* we aren't a complete listener.
|
|
* This will periodically until the complete timeout.
|
|
*/
|
|
private class Watcher extends SimpleTimer2.TimedEvent {
|
|
private final long _start = _context.clock().now();
|
|
|
|
public Watcher() {
|
|
super(_context.simpleTimer2(), CHECK_INTERVAL);
|
|
}
|
|
|
|
public void timeReached() {
|
|
if (_hasMetaInfo && _snark.getRemainingLength() == 0 && !_isComplete)
|
|
processComplete(_snark);
|
|
if (_isComplete || !_isRunning)
|
|
return;
|
|
if (_context.clock().now() - _start >= METAINFO_TIMEOUT && !_hasMetaInfo) {
|
|
fatal("Metainfo timeout");
|
|
return;
|
|
}
|
|
if (_context.clock().now() - _start >= COMPLETE_TIMEOUT) {
|
|
fatal("Complete timeout");
|
|
return;
|
|
}
|
|
notifyProgress();
|
|
reschedule(CHECK_INTERVAL);
|
|
}
|
|
}
|
|
|
|
private void fatal(String error) {
|
|
if (_snark != null) {
|
|
if (_hasMetaInfo) {
|
|
_smgr.stopTorrent(_snark, true);
|
|
String file = _snark.getName();
|
|
_smgr.removeTorrent(file);
|
|
// delete torrent file
|
|
File f = new File(_smgr.getDataDir(), file);
|
|
f.delete();
|
|
// delete data
|
|
file = _snark.getBaseName();
|
|
f = new File(_smgr.getDataDir(), file);
|
|
f.delete();
|
|
} else {
|
|
_smgr.deleteMagnet(_snark);
|
|
}
|
|
}
|
|
_umgr.notifyTaskFailed(this, error, null);
|
|
_log.error(error);
|
|
_isRunning = false;
|
|
// stop the tunnel if we were the only one running
|
|
if (_smgr.util().connected() && !_smgr.util().isConnecting()) {
|
|
for (Snark s : _smgr.getTorrents()) {
|
|
if (!s.isStopped())
|
|
return;
|
|
}
|
|
_smgr.util().disconnect();
|
|
}
|
|
}
|
|
|
|
private void processComplete(Snark snark) {
|
|
String dataFile = snark.getBaseName();
|
|
File f = new File(_smgr.getDataDir(), dataFile);
|
|
String sudVersion = TrustedUpdate.getVersionString(f);
|
|
if (_newVersion.equals(sudVersion))
|
|
_umgr.notifyComplete(this, _newVersion, f);
|
|
else
|
|
fatal("version mismatch");
|
|
_isComplete = true;
|
|
}
|
|
|
|
private void notifyProgress() {
|
|
if (_hasMetaInfo) {
|
|
long total = _snark.getTotalLength();
|
|
long remaining = _snark.getRemainingLength();
|
|
String status = "<b>" + _smgr.util().getString("Updating") + "</b>";
|
|
_umgr.notifyProgress(this, status, total - remaining, total);
|
|
}
|
|
}
|
|
|
|
//////// begin CompleteListener methods
|
|
//////// all pass through to SnarkManager
|
|
|
|
public void torrentComplete(Snark snark) {
|
|
processComplete(snark);
|
|
_smgr.torrentComplete(snark);
|
|
}
|
|
|
|
/**
|
|
* This is called by stopTorrent() among others
|
|
*/
|
|
public void updateStatus(Snark snark) {
|
|
if (snark.isStopped()) {
|
|
if (!_isComplete)
|
|
fatal("stopped by user");
|
|
}
|
|
_smgr.updateStatus(snark);
|
|
}
|
|
|
|
public String gotMetaInfo(Snark snark) {
|
|
MetaInfo info = snark.getMetaInfo();
|
|
if (info.getFiles() != null) {
|
|
fatal("more than 1 file");
|
|
return null;
|
|
}
|
|
if (info.isPrivate()) {
|
|
fatal("private torrent");
|
|
return null;
|
|
}
|
|
if (info.getTotalLength() > MAX_LENGTH) {
|
|
fatal("too big");
|
|
return null;
|
|
}
|
|
_hasMetaInfo = true;
|
|
notifyProgress();
|
|
return _smgr.gotMetaInfo(snark);
|
|
}
|
|
|
|
public void fatal(Snark snark, String error) {
|
|
fatal(error);
|
|
_smgr.fatal(snark, error);
|
|
}
|
|
|
|
public void addMessage(Snark snark, String message) {
|
|
_smgr.addMessage(snark, message);
|
|
}
|
|
|
|
public void gotPiece(Snark snark) {
|
|
notifyProgress();
|
|
_smgr.gotPiece(snark);
|
|
}
|
|
|
|
public long getSavedTorrentTime(Snark snark) {
|
|
return _smgr.getSavedTorrentTime(snark);
|
|
}
|
|
|
|
public BitField getSavedTorrentBitField(Snark snark) {
|
|
return _smgr.getSavedTorrentBitField(snark);
|
|
}
|
|
|
|
//////// end CompleteListener methods
|
|
|
|
private static String linkify(String url) {
|
|
String durl = url.length() <= 28 ? url :
|
|
url.substring(0, 25) + "…";
|
|
return "<a target=\"_blank\" href=\"" + url + "\"/>" + durl + "</a>";
|
|
}
|
|
|
|
private void updateStatus(String s) {
|
|
_umgr.notifyProgress(this, s);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return getClass().getName() + ' ' + getType() + ' ' + getID() + ' ' + getMethod() + ' ' + getURI();
|
|
}
|
|
}
|