i2pd/Reseed.cpp

470 lines
14 KiB
C++
Raw Normal View History

2014-12-12 20:45:39 +00:00
#include <string.h>
#include <fstream>
2014-12-11 20:41:04 +00:00
#include <sstream>
2015-11-03 14:15:49 +00:00
#include <boost/asio.hpp>
2016-05-11 20:02:26 +00:00
#include <boost/asio/ssl.hpp>
#include <openssl/ssl.h>
#include <openssl/err.h>
2015-11-03 14:15:49 +00:00
#include <zlib.h>
2016-05-11 20:02:26 +00:00
#include "Crypto.h"
#include "I2PEndian.h"
#include "Reseed.h"
#include "FS.h"
#include "Log.h"
2014-12-09 21:44:58 +00:00
#include "Identity.h"
2014-12-11 20:41:04 +00:00
#include "NetDb.h"
2016-07-19 00:00:00 +00:00
#include "HTTP.h"
2016-07-21 01:44:17 +00:00
#include "util.h"
2016-08-12 14:33:53 +00:00
#include "Config.h"
namespace i2p
{
namespace data
{
2016-06-27 13:47:53 +00:00
static std::vector<std::string> httpsReseedHostList =
2015-11-03 14:15:49 +00:00
{
2016-06-27 13:47:53 +00:00
#ifdef MESHNET
// meshnet i2p reseeds
"https://reseed.i2p.rocks:8443/"
#else
// mainline i2p reseeds
2015-11-03 14:15:49 +00:00
"https://reseed.i2p-projekt.de/", // Only HTTPS
2016-08-13 13:05:35 +00:00
"https://i2p.mooo.com/netDb/",
"https://netdb.i2p2.no/", // Only SU3 (v3) support, SNI required
2015-11-03 14:15:49 +00:00
"https://us.reseed.i2p2.no:444/",
2016-08-13 13:05:35 +00:00
"https://uk.reseed.i2p2.no:444/",
2016-03-21 13:02:44 +00:00
"https://i2p.manas.ca:8443/",
"https://i2p-0.manas.ca:8443/",
2016-08-13 13:05:35 +00:00
"https://reseed.i2p.vzaws.com:8443/", // Only SU3 (v3) support
"https://user.mx24.eu/", // Only HTTPS and SU3 (v3) support
"https://download.xxlspeed.com/", // Only HTTPS and SU3 (v3) support
"https://reseed-ru.lngserv.ru/"
2016-06-27 13:47:53 +00:00
#endif
2015-11-03 14:15:49 +00:00
};
2016-06-27 13:47:53 +00:00
Reseeder::Reseeder()
{
}
Reseeder::~Reseeder()
{
}
2014-12-11 20:41:04 +00:00
int Reseeder::ReseedNowSU3 ()
{
2016-08-12 14:33:53 +00:00
std::string filename; i2p::config::GetOption("reseed.file", filename);
if (filename.length() > 0) // reseed file is specified
{
auto num = ProcessSU3File (filename.c_str ());
if (num > 0) return num; // success
LogPrint (eLogWarning, "Can't reseed from ", filename, " . Trying from hosts");
}
2015-11-03 14:15:49 +00:00
auto ind = rand () % httpsReseedHostList.size ();
std::string& reseedHost = httpsReseedHostList[ind];
2015-12-21 14:33:09 +00:00
return ReseedFromSU3 (reseedHost);
2014-12-11 20:41:04 +00:00
}
2015-12-21 14:33:09 +00:00
int Reseeder::ReseedFromSU3 (const std::string& host)
2014-12-11 20:41:04 +00:00
{
std::string url = host + "i2pseeds.su3";
2015-12-21 02:20:16 +00:00
LogPrint (eLogInfo, "Reseed: Downloading SU3 from ", host);
2015-12-21 03:46:35 +00:00
std::string su3 = HttpsRequest (url);
2014-12-11 20:41:04 +00:00
if (su3.length () > 0)
{
std::stringstream s(su3);
return ProcessSU3Stream (s);
}
else
{
2015-12-21 02:20:16 +00:00
LogPrint (eLogWarning, "Reseed: SU3 download failed");
2014-12-11 20:41:04 +00:00
return 0;
}
}
2014-12-09 21:44:58 +00:00
2014-12-12 15:34:50 +00:00
int Reseeder::ProcessSU3File (const char * filename)
2014-12-11 20:41:04 +00:00
{
std::ifstream s(filename, std::ifstream::binary);
if (s.is_open ())
return ProcessSU3Stream (s);
else
{
2015-12-21 02:20:16 +00:00
LogPrint (eLogError, "Reseed: Can't open file ", filename);
2014-12-11 20:41:04 +00:00
return 0;
}
}
2014-12-09 21:44:58 +00:00
const char SU3_MAGIC_NUMBER[]="I2Psu3";
2014-12-12 15:34:50 +00:00
const uint32_t ZIP_HEADER_SIGNATURE = 0x04034B50;
2014-12-16 17:48:42 +00:00
const uint32_t ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014B50;
2014-12-12 15:34:50 +00:00
const uint16_t ZIP_BIT_FLAG_DATA_DESCRIPTOR = 0x0008;
int Reseeder::ProcessSU3Stream (std::istream& s)
2014-11-20 19:29:22 +00:00
{
2014-12-11 20:41:04 +00:00
char magicNumber[7];
s.read (magicNumber, 7); // magic number and zero byte 6
if (strcmp (magicNumber, SU3_MAGIC_NUMBER))
{
2015-12-21 02:20:16 +00:00
LogPrint (eLogError, "Reseed: Unexpected SU3 magic number");
2014-12-11 20:41:04 +00:00
return 0;
}
s.seekg (1, std::ios::cur); // su3 file format version
SigningKeyType signatureType;
s.read ((char *)&signatureType, 2); // signature type
signatureType = be16toh (signatureType);
uint16_t signatureLength;
s.read ((char *)&signatureLength, 2); // signature length
signatureLength = be16toh (signatureLength);
s.seekg (1, std::ios::cur); // unused
uint8_t versionLength;
s.read ((char *)&versionLength, 1); // version length
s.seekg (1, std::ios::cur); // unused
uint8_t signerIDLength;
s.read ((char *)&signerIDLength, 1); // signer ID length
uint64_t contentLength;
s.read ((char *)&contentLength, 8); // content length
contentLength = be64toh (contentLength);
s.seekg (1, std::ios::cur); // unused
uint8_t fileType;
s.read ((char *)&fileType, 1); // file type
if (fileType != 0x00) // zip file
{
2015-12-21 02:20:16 +00:00
LogPrint (eLogError, "Reseed: Can't handle file type ", (int)fileType);
2014-12-11 20:41:04 +00:00
return 0;
}
s.seekg (1, std::ios::cur); // unused
uint8_t contentType;
s.read ((char *)&contentType, 1); // content type
if (contentType != 0x03) // reseed data
{
2015-12-21 02:20:16 +00:00
LogPrint (eLogError, "Reseed: Unexpected content type ", (int)contentType);
2014-12-11 20:41:04 +00:00
return 0;
}
s.seekg (12, std::ios::cur); // unused
s.seekg (versionLength, std::ios::cur); // skip version
char signerID[256];
s.read (signerID, signerIDLength); // signerID
signerID[signerIDLength] = 0;
//try to verify signature
auto it = m_SigningKeys.find (signerID);
if (it != m_SigningKeys.end ())
{
// TODO: implement all signature types
if (signatureType == SIGNING_KEY_TYPE_RSA_SHA512_4096)
{
size_t pos = s.tellg ();
size_t tbsLen = pos + contentLength;
uint8_t * tbs = new uint8_t[tbsLen];
s.seekg (0, std::ios::beg);
s.read ((char *)tbs, tbsLen);
uint8_t * signature = new uint8_t[signatureLength];
s.read ((char *)signature, signatureLength);
2014-12-15 03:45:09 +00:00
// RSA-raw
2015-11-03 14:15:49 +00:00
{
// calculate digest
uint8_t digest[64];
SHA512 (tbs, tbsLen, digest);
// encrypt signature
BN_CTX * bnctx = BN_CTX_new ();
BIGNUM * s = BN_new (), * n = BN_new ();
BN_bin2bn (signature, signatureLength, s);
BN_bin2bn (it->second, i2p::crypto::RSASHA5124096_KEY_LENGTH, n);
BN_mod_exp (s, s, i2p::crypto::GetRSAE (), n, bnctx); // s = s^e mod n
2015-11-03 14:15:49 +00:00
uint8_t * enSigBuf = new uint8_t[signatureLength];
i2p::crypto::bn2buf (s, enSigBuf, signatureLength);
// digest is right aligned
// we can't use RSA_verify due wrong padding in SU3
if (memcmp (enSigBuf + (signatureLength - 64), digest, 64))
2015-12-21 02:20:16 +00:00
LogPrint (eLogWarning, "Reseed: SU3 signature verification failed");
2015-11-03 14:15:49 +00:00
delete[] enSigBuf;
BN_free (s); BN_free (n);
BN_CTX_free (bnctx);
}
delete[] signature;
delete[] tbs;
s.seekg (pos, std::ios::beg);
}
else
2015-12-21 02:20:16 +00:00
LogPrint (eLogWarning, "Reseed: Signature type ", signatureType, " is not supported");
}
else
2015-12-21 02:20:16 +00:00
LogPrint (eLogWarning, "Reseed: Certificate for ", signerID, " not loaded");
2014-12-11 20:41:04 +00:00
// handle content
int numFiles = 0;
size_t contentPos = s.tellg ();
while (!s.eof ())
2014-11-20 19:29:22 +00:00
{
2014-12-11 20:41:04 +00:00
uint32_t signature;
s.read ((char *)&signature, 4);
2014-12-12 15:34:50 +00:00
signature = le32toh (signature);
if (signature == ZIP_HEADER_SIGNATURE)
2014-12-09 21:44:58 +00:00
{
2014-12-11 20:41:04 +00:00
// next local file
2014-12-12 15:34:50 +00:00
s.seekg (2, std::ios::cur); // version
uint16_t bitFlag;
s.read ((char *)&bitFlag, 2);
bitFlag = le16toh (bitFlag);
2014-12-12 01:44:08 +00:00
uint16_t compressionMethod;
s.read ((char *)&compressionMethod, 2);
compressionMethod = le16toh (compressionMethod);
2014-12-16 17:48:42 +00:00
s.seekg (4, std::ios::cur); // skip fields we don't care about
uint32_t compressedSize, uncompressedSize;
2015-11-03 14:15:49 +00:00
uint32_t crc_32;
s.read ((char *)&crc_32, 4);
crc_32 = le32toh (crc_32);
2014-12-11 20:41:04 +00:00
s.read ((char *)&compressedSize, 4);
compressedSize = le32toh (compressedSize);
s.read ((char *)&uncompressedSize, 4);
uncompressedSize = le32toh (uncompressedSize);
uint16_t fileNameLength, extraFieldLength;
s.read ((char *)&fileNameLength, 2);
2014-12-12 01:44:08 +00:00
fileNameLength = le16toh (fileNameLength);
if ( fileNameLength > 255 ) {
// too big
LogPrint(eLogError, "Reseed: SU3 fileNameLength too large: ", fileNameLength);
return numFiles;
}
2014-12-11 20:41:04 +00:00
s.read ((char *)&extraFieldLength, 2);
2014-12-12 01:44:08 +00:00
extraFieldLength = le16toh (extraFieldLength);
2014-12-11 20:41:04 +00:00
char localFileName[255];
s.read (localFileName, fileNameLength);
localFileName[fileNameLength] = 0;
s.seekg (extraFieldLength, std::ios::cur);
2014-12-12 18:36:02 +00:00
// take care about data desriptor if presented
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
{
size_t pos = s.tellg ();
if (!FindZipDataDescriptor (s))
{
2015-12-21 02:20:16 +00:00
LogPrint (eLogError, "Reseed: SU3 archive data descriptor not found");
2014-12-12 18:36:02 +00:00
return numFiles;
2015-11-03 14:15:49 +00:00
}
s.read ((char *)&crc_32, 4);
crc_32 = le32toh (crc_32);
2014-12-12 18:36:02 +00:00
s.read ((char *)&compressedSize, 4);
compressedSize = le32toh (compressedSize) + 4; // ??? we must consider signature as part of compressed data
s.read ((char *)&uncompressedSize, 4);
uncompressedSize = le32toh (uncompressedSize);
// now we know compressed and uncompressed size
s.seekg (pos, std::ios::beg); // back to compressed data
}
2015-12-21 02:20:16 +00:00
LogPrint (eLogDebug, "Reseed: Proccessing file ", localFileName, " ", compressedSize, " bytes");
2014-12-12 03:31:39 +00:00
if (!compressedSize)
{
2015-12-21 02:20:16 +00:00
LogPrint (eLogWarning, "Reseed: Unexpected size 0. Skipped");
2014-12-12 03:31:39 +00:00
continue;
}
2014-12-11 20:41:04 +00:00
uint8_t * compressed = new uint8_t[compressedSize];
s.read ((char *)compressed, compressedSize);
2014-12-12 01:44:08 +00:00
if (compressionMethod) // we assume Deflate
{
2015-11-03 14:15:49 +00:00
z_stream inflator;
memset (&inflator, 0, sizeof (inflator));
inflateInit2 (&inflator, -MAX_WBITS); // no zlib header
uint8_t * uncompressed = new uint8_t[uncompressedSize];
inflator.next_in = compressed;
inflator.avail_in = compressedSize;
inflator.next_out = uncompressed;
inflator.avail_out = uncompressedSize;
int err;
if ((err = inflate (&inflator, Z_SYNC_FLUSH)) >= 0)
{
uncompressedSize -= inflator.avail_out;
if (crc32 (0, uncompressed, uncompressedSize) == crc_32)
2014-12-16 17:48:42 +00:00
{
i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize);
numFiles++;
2015-11-03 14:15:49 +00:00
}
2014-12-16 17:48:42 +00:00
else
2015-12-21 02:20:16 +00:00
LogPrint (eLogError, "Reseed: CRC32 verification failed");
2015-11-03 14:15:49 +00:00
}
2014-12-12 01:44:08 +00:00
else
2015-12-21 02:20:16 +00:00
LogPrint (eLogError, "Reseed: SU3 decompression error ", err);
2015-11-03 14:15:49 +00:00
delete[] uncompressed;
inflateEnd (&inflator);
}
2014-12-12 01:44:08 +00:00
else // no compression
2014-11-20 19:29:22 +00:00
{
2014-12-12 01:44:08 +00:00
i2p::data::netdb.AddRouterInfo (compressed, compressedSize);
2014-12-11 20:41:04 +00:00
numFiles++;
2014-12-12 01:44:08 +00:00
}
delete[] compressed;
2014-12-12 18:36:02 +00:00
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR)
s.seekg (12, std::ios::cur); // skip data descriptor section if presented (12 = 16 - 4)
2014-11-20 19:29:22 +00:00
}
2014-12-11 20:41:04 +00:00
else
2014-12-16 17:48:42 +00:00
{
if (signature != ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE)
2015-12-21 02:20:16 +00:00
LogPrint (eLogWarning, "Reseed: Missing zip central directory header");
2014-12-11 20:41:04 +00:00
break; // no more files
2014-12-16 17:48:42 +00:00
}
2014-12-11 20:41:04 +00:00
size_t end = s.tellg ();
if (end - contentPos >= contentLength)
break; // we are beyond contentLength
2014-11-20 19:29:22 +00:00
}
2014-12-11 20:41:04 +00:00
return numFiles;
2014-11-20 19:29:22 +00:00
}
2014-12-12 18:36:02 +00:00
const uint8_t ZIP_DATA_DESCRIPTOR_SIGNATURE[] = { 0x50, 0x4B, 0x07, 0x08 };
bool Reseeder::FindZipDataDescriptor (std::istream& s)
{
size_t nextInd = 0;
while (!s.eof ())
{
uint8_t nextByte;
s.read ((char *)&nextByte, 1);
if (nextByte == ZIP_DATA_DESCRIPTOR_SIGNATURE[nextInd])
{
nextInd++;
if (nextInd >= sizeof (ZIP_DATA_DESCRIPTOR_SIGNATURE))
return true;
}
else
nextInd = 0;
}
2014-12-17 16:42:16 +00:00
return false;
2014-12-12 18:36:02 +00:00
}
2014-12-12 20:45:39 +00:00
void Reseeder::LoadCertificate (const std::string& filename)
{
2015-11-03 14:15:49 +00:00
SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ());
int ret = SSL_CTX_use_certificate_file (ctx, filename.c_str (), SSL_FILETYPE_PEM);
if (ret)
{
SSL * ssl = SSL_new (ctx);
X509 * cert = SSL_get_certificate (ssl);
// verify
if (cert)
{
// extract issuer name
char name[100];
X509_NAME_oneline (X509_get_issuer_name(cert), name, 100);
2016-08-26 13:48:19 +00:00
char * cn = strstr (name, "CN=");
if (cn)
{
cn += 3;
char * terminator = strchr (cn, '/');
if (terminator) terminator[0] = 0;
}
2015-11-03 14:15:49 +00:00
// extract RSA key (we need n only, e = 65537)
RSA * key = X509_get_pubkey (cert)->pkey.rsa;
PublicKey value;
i2p::crypto::bn2buf (key->n, value, 512);
2016-08-26 13:48:19 +00:00
if (cn)
m_SigningKeys[cn] = value;
else
LogPrint (eLogError, "Reseed: Can't find CN field in ", filename);
2014-12-12 20:45:39 +00:00
}
2015-11-03 14:15:49 +00:00
SSL_free (ssl);
}
2014-12-12 20:45:39 +00:00
else
2015-12-21 02:20:16 +00:00
LogPrint (eLogError, "Reseed: Can't open certificate file ", filename);
2015-11-03 14:15:49 +00:00
SSL_CTX_free (ctx);
2014-12-12 20:45:39 +00:00
}
void Reseeder::LoadCertificates ()
{
std::string certDir = i2p::fs::DataDirPath("certificates", "reseed");
std::vector<std::string> files;
int numCertificates = 0;
if (!i2p::fs::ReadDir(certDir, files)) {
LogPrint(eLogWarning, "Reseed: Can't load reseed certificates from ", certDir);
return;
}
for (const std::string & file : files) {
if (file.compare(file.size() - 4, 4, ".crt") != 0) {
LogPrint(eLogWarning, "Reseed: ignoring file ", file);
continue;
}
LoadCertificate (file);
numCertificates++;
}
2015-12-21 02:20:16 +00:00
LogPrint (eLogInfo, "Reseed: ", numCertificates, " certificates loaded");
}
2015-02-16 04:03:04 +00:00
2015-02-20 15:47:44 +00:00
std::string Reseeder::HttpsRequest (const std::string& address)
{
2016-07-19 00:00:00 +00:00
i2p::http::URL url;
if (!url.parse(address)) {
LogPrint(eLogError, "Reseed: failed to parse url: ", address);
return "";
}
url.schema = "https";
if (!url.port)
url.port = 443;
2015-11-03 14:15:49 +00:00
boost::asio::io_service service;
boost::system::error_code ecode;
2016-07-19 00:00:00 +00:00
auto it = boost::asio::ip::tcp::resolver(service).resolve (
boost::asio::ip::tcp::resolver::query (url.host, std::to_string(url.port)), ecode);
2015-11-03 14:15:49 +00:00
if (!ecode)
{
2015-11-06 14:01:02 +00:00
boost::asio::ssl::context ctx(service, boost::asio::ssl::context::sslv23);
2015-11-03 14:15:49 +00:00
ctx.set_verify_mode(boost::asio::ssl::context::verify_none);
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> s(service, ctx);
s.lowest_layer().connect (*it, ecode);
if (!ecode)
2015-04-02 00:23:06 +00:00
{
2015-11-03 14:15:49 +00:00
s.handshake (boost::asio::ssl::stream_base::client, ecode);
if (!ecode)
{
2016-07-19 00:00:00 +00:00
LogPrint (eLogDebug, "Reseed: Connected to ", url.host, ":", url.port);
i2p::http::HTTPReq req;
req.uri = url.to_string();
req.add_header("User-Agent", "Wget/1.11.4");
req.add_header("Connection", "close");
s.write_some (boost::asio::buffer (req.to_string()));
2015-11-03 14:15:49 +00:00
// read response
std::stringstream rs;
char recv_buf[1024]; size_t l = 0;
do {
l = s.read_some (boost::asio::buffer (recv_buf, sizeof(recv_buf)), ecode);
if (l) rs.write (recv_buf, l);
} while (!ecode && l);
2015-11-03 14:15:49 +00:00
// process response
std::string data = rs.str();
i2p::http::HTTPRes res;
int len = res.parse(data);
if (len <= 0) {
LogPrint(eLogWarning, "Reseed: incomplete/broken response from ", url.host);
return "";
}
2016-07-19 00:00:00 +00:00
if (res.code != 200) {
LogPrint(eLogError, "Reseed: failed to reseed from ", url.host, ", http code ", res.code);
return "";
}
data.erase(0, len); /* drop http headers from response */
LogPrint(eLogDebug, "Reseed: got ", data.length(), " bytes of data from ", url.host);
if (res.is_chunked()) {
std::stringstream in(data), out;
if (!i2p::http::MergeChunkedResponse(in, out)) {
LogPrint(eLogWarning, "Reseed: failed to merge chunked response from ", url.host);
return "";
}
LogPrint(eLogDebug, "Reseed: got ", data.length(), "(", out.tellg(), ") bytes of data from ", url.host);
data = out.str();
}
return data;
2015-11-03 14:15:49 +00:00
}
else
2015-12-21 02:20:16 +00:00
LogPrint (eLogError, "Reseed: SSL handshake failed: ", ecode.message ());
2015-04-02 00:23:06 +00:00
}
2015-11-03 14:15:49 +00:00
else
2016-07-19 00:00:00 +00:00
LogPrint (eLogError, "Reseed: Couldn't connect to ", url.host, ": ", ecode.message ());
2015-04-01 18:41:36 +00:00
}
else
2016-07-19 00:00:00 +00:00
LogPrint (eLogError, "Reseed: Couldn't resolve address ", url.host, ": ", ecode.message ());
2015-11-03 14:15:49 +00:00
return "";
}
}
}