mirror of https://github.com/PurpleI2P/i2pd.git
I2P: End-to-End encrypted and anonymous Internet
https://i2pd.website/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
486 lines
14 KiB
486 lines
14 KiB
#include <string.h> |
|
#include <fstream> |
|
#include <sstream> |
|
#include <boost/regex.hpp> |
|
#include <boost/filesystem.hpp> |
|
#include <cryptopp/osrng.h> |
|
#include <cryptopp/asn.h> |
|
#include <cryptopp/base64.h> |
|
#include <cryptopp/zinflate.h> |
|
#include "I2PEndian.h" |
|
#include "Reseed.h" |
|
#include "Log.h" |
|
#include "Identity.h" |
|
#include "Signature.h" |
|
#include "NetDb.h" |
|
#include "util.h" |
|
|
|
|
|
namespace i2p |
|
{ |
|
namespace data |
|
{ |
|
|
|
static std::vector<std::string> httpReseedHostList = { |
|
"http://193.150.121.66/netDb/", |
|
"http://netdb.i2p2.no/", |
|
"http://reseed.i2p-projekt.de/", |
|
"http://cowpuncher.drollette.com/netdb/", |
|
"http://i2p.mooo.com/netDb/", |
|
"http://reseed.info/", |
|
"http://uk.reseed.i2p2.no/", |
|
"http://us.reseed.i2p2.no/", |
|
"http://jp.reseed.i2p2.no/", |
|
"http://i2p-netdb.innovatio.no/", |
|
"http://ieb9oopo.mooo.com" |
|
}; |
|
|
|
//TODO: Remember to add custom port support. Not all serves on 443 |
|
static std::vector<std::string> httpsReseedHostList = { |
|
"https://193.150.121.66/netDb/", |
|
"https://netdb.i2p2.no/", |
|
"https://reseed.i2p-projekt.de/", |
|
"https://cowpuncher.drollette.com/netdb/", |
|
"https://i2p.mooo.com/netDb/", |
|
"https://reseed.info/", |
|
"https://i2p-netdb.innovatio.no/", |
|
"https://ieb9oopo.mooo.com/", |
|
"https://ssl.webpack.de/ivae2he9.sg4.e-plaza.de/" // Only HTTPS and SU3 (v2) support |
|
}; |
|
|
|
//TODO: Implement v2 reseeding. Lightweight zip library is needed. |
|
//TODO: Implement SU3, utils. |
|
Reseeder::Reseeder() |
|
{ |
|
} |
|
|
|
Reseeder::~Reseeder() |
|
{ |
|
} |
|
|
|
bool Reseeder::reseedNow() |
|
{ |
|
try |
|
{ |
|
// Seems like the best place to try to intercept with SSL |
|
/*ssl_server = true; |
|
try { |
|
// SSL |
|
} |
|
catch (std::exception& e) |
|
{ |
|
LogPrint("Exception in SSL: ", e.what()); |
|
}*/ |
|
std::string reseedHost = httpReseedHostList[(rand() % httpReseedHostList.size())]; |
|
LogPrint("Reseeding from ", reseedHost); |
|
std::string content = i2p::util::http::httpRequest(reseedHost); |
|
if (content == "") |
|
{ |
|
LogPrint("Reseed failed"); |
|
return false; |
|
} |
|
boost::regex e("<\\s*A\\s+[^>]*href\\s*=\\s*\"([^\"]*)\"", boost::regex::normal | boost::regbase::icase); |
|
boost::sregex_token_iterator i(content.begin(), content.end(), e, 1); |
|
boost::sregex_token_iterator j; |
|
//TODO: Ugly code, try to clean up. |
|
//TODO: Try to reduce N number of variables |
|
std::string name; |
|
std::string routerInfo; |
|
std::string tmpUrl; |
|
std::string filename; |
|
std::string ignoreFileSuffix = ".su3"; |
|
boost::filesystem::path root = i2p::util::filesystem::GetDataDir(); |
|
while (i != j) |
|
{ |
|
name = *i++; |
|
if (name.find(ignoreFileSuffix)!=std::string::npos) |
|
continue; |
|
LogPrint("Downloading ", name); |
|
tmpUrl = reseedHost; |
|
tmpUrl.append(name); |
|
routerInfo = i2p::util::http::httpRequest(tmpUrl); |
|
if (routerInfo.size()==0) |
|
continue; |
|
filename = root.string(); |
|
#ifndef _WIN32 |
|
filename += "/netDb/r"; |
|
#else |
|
filename += "\\netDb\\r"; |
|
#endif |
|
filename += name.at(11); // first char in id |
|
#ifndef _WIN32 |
|
filename.append("/"); |
|
#else |
|
filename.append("\\"); |
|
#endif |
|
filename.append(name.c_str()); |
|
std::ofstream outfile (filename, std::ios::binary); |
|
outfile << routerInfo; |
|
outfile.close(); |
|
} |
|
return true; |
|
} |
|
catch (std::exception& ex) |
|
{ |
|
//TODO: error reporting |
|
return false; |
|
} |
|
return false; |
|
} |
|
|
|
int Reseeder::ReseedNowSU3 () |
|
{ |
|
CryptoPP::AutoSeededRandomPool rnd; |
|
auto ind = rnd.GenerateWord32 (0, httpReseedHostList.size() - 1); |
|
std::string reseedHost = httpReseedHostList[ind]; |
|
return ReseedFromSU3 (reseedHost); |
|
} |
|
|
|
int Reseeder::ReseedFromSU3 (const std::string& host) |
|
{ |
|
std::string url = host + "i2pseeds.su3"; |
|
LogPrint (eLogInfo, "Dowloading SU3 from ", host); |
|
std::string su3 = i2p::util::http::httpRequest (url); |
|
if (su3.length () > 0) |
|
{ |
|
std::stringstream s(su3); |
|
return ProcessSU3Stream (s); |
|
} |
|
else |
|
{ |
|
LogPrint (eLogWarning, "SU3 download failed"); |
|
return 0; |
|
} |
|
} |
|
|
|
int Reseeder::ProcessSU3File (const char * filename) |
|
{ |
|
std::ifstream s(filename, std::ifstream::binary); |
|
if (s.is_open ()) |
|
return ProcessSU3Stream (s); |
|
else |
|
{ |
|
LogPrint (eLogError, "Can't open file ", filename); |
|
return 0; |
|
} |
|
} |
|
|
|
const char SU3_MAGIC_NUMBER[]="I2Psu3"; |
|
const uint32_t ZIP_HEADER_SIGNATURE = 0x04034B50; |
|
const uint16_t ZIP_BIT_FLAG_DATA_DESCRIPTOR = 0x0008; |
|
int Reseeder::ProcessSU3Stream (std::istream& s) |
|
{ |
|
char magicNumber[7]; |
|
s.read (magicNumber, 7); // magic number and zero byte 6 |
|
if (strcmp (magicNumber, SU3_MAGIC_NUMBER)) |
|
{ |
|
LogPrint (eLogError, "Unexpected SU3 magic number"); |
|
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 |
|
{ |
|
LogPrint (eLogError, "Can't handle file type ", (int)fileType); |
|
return 0; |
|
} |
|
s.seekg (1, std::ios::cur); // unused |
|
uint8_t contentType; |
|
s.read ((char *)&contentType, 1); // content type |
|
if (contentType != 0x03) // reseed data |
|
{ |
|
LogPrint (eLogError, "Unexpected content type ", (int)contentType); |
|
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) |
|
{ |
|
i2p::crypto::RSASHA5124096Verifier verifier(it->second); |
|
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); |
|
if (!verifier.Verify (tbs, tbsLen, signature)) |
|
LogPrint (eLogWarning, "SU3 signature verification failed"); |
|
delete[] signature; |
|
delete[] tbs; |
|
s.seekg (pos, std::ios::beg); |
|
} |
|
else |
|
LogPrint (eLogWarning, "Signature type ", signatureType, " is not supported"); |
|
} |
|
else |
|
LogPrint (eLogWarning, "Certificate for ", signerID, " not loaded"); |
|
|
|
// handle content |
|
int numFiles = 0; |
|
size_t contentPos = s.tellg (); |
|
while (!s.eof ()) |
|
{ |
|
uint32_t signature; |
|
s.read ((char *)&signature, 4); |
|
signature = le32toh (signature); |
|
if (signature == ZIP_HEADER_SIGNATURE) |
|
{ |
|
// next local file |
|
s.seekg (2, std::ios::cur); // version |
|
uint16_t bitFlag; |
|
s.read ((char *)&bitFlag, 2); |
|
bitFlag = le16toh (bitFlag); |
|
uint16_t compressionMethod; |
|
s.read ((char *)&compressionMethod, 2); |
|
compressionMethod = le16toh (compressionMethod); |
|
s.seekg (8, std::ios::cur); // skip fields we don't care about |
|
uint32_t compressedSize, uncompressedSize; |
|
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); |
|
fileNameLength = le16toh (fileNameLength); |
|
s.read ((char *)&extraFieldLength, 2); |
|
extraFieldLength = le16toh (extraFieldLength); |
|
char localFileName[255]; |
|
s.read (localFileName, fileNameLength); |
|
localFileName[fileNameLength] = 0; |
|
s.seekg (extraFieldLength, std::ios::cur); |
|
// take care about data desriptor if presented |
|
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR) |
|
{ |
|
size_t pos = s.tellg (); |
|
if (!FindZipDataDescriptor (s)) |
|
{ |
|
LogPrint (eLogError, "SU3 archive data descriptor not found"); |
|
return numFiles; |
|
} |
|
|
|
s.seekg (4, std::ios::cur); // skip CRC-32 |
|
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 |
|
} |
|
|
|
LogPrint (eLogDebug, "Proccessing file ", localFileName, " ", compressedSize, " bytes"); |
|
if (!compressedSize) |
|
{ |
|
LogPrint (eLogWarning, "Unexpected size 0. Skipped"); |
|
continue; |
|
} |
|
|
|
uint8_t * compressed = new uint8_t[compressedSize]; |
|
s.read ((char *)compressed, compressedSize); |
|
if (compressionMethod) // we assume Deflate |
|
{ |
|
CryptoPP::Inflator decompressor; |
|
decompressor.Put (compressed, compressedSize); |
|
decompressor.MessageEnd(); |
|
if (decompressor.MaxRetrievable () <= uncompressedSize) |
|
{ |
|
uint8_t * uncompressed = new uint8_t[uncompressedSize]; |
|
decompressor.Get (uncompressed, uncompressedSize); |
|
i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize); |
|
numFiles++; |
|
delete[] uncompressed; |
|
} |
|
else |
|
LogPrint (eLogError, "Actual uncompressed size ", decompressor.MaxRetrievable (), " exceed ", uncompressedSize, " from header"); |
|
} |
|
else // no compression |
|
{ |
|
i2p::data::netdb.AddRouterInfo (compressed, compressedSize); |
|
numFiles++; |
|
} |
|
delete[] compressed; |
|
if (bitFlag & ZIP_BIT_FLAG_DATA_DESCRIPTOR) |
|
s.seekg (12, std::ios::cur); // skip data descriptor section if presented (12 = 16 - 4) |
|
} |
|
else |
|
break; // no more files |
|
size_t end = s.tellg (); |
|
if (end - contentPos >= contentLength) |
|
break; // we are beyond contentLength |
|
} |
|
return numFiles; |
|
} |
|
|
|
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; |
|
} |
|
return s; |
|
} |
|
|
|
const char CERTIFICATE_HEADER[] = "-----BEGIN CERTIFICATE-----"; |
|
const char CERTIFICATE_FOOTER[] = "-----END CERTIFICATE-----"; |
|
void Reseeder::LoadCertificate (const std::string& filename) |
|
{ |
|
std::ifstream s(filename, std::ifstream::binary); |
|
if (s.is_open ()) |
|
{ |
|
s.seekg (0, std::ios::end); |
|
size_t len = s.tellg (); |
|
s.seekg (0, std::ios::beg); |
|
char buf[2048]; |
|
s.read (buf, len); |
|
std::string cert (buf, len); |
|
// assume file in pem format |
|
auto pos1 = cert.find (CERTIFICATE_HEADER); |
|
auto pos2 = cert.find (CERTIFICATE_FOOTER); |
|
if (pos1 == std::string::npos || pos2 == std::string::npos) |
|
{ |
|
LogPrint (eLogError, "Malformed certificate file"); |
|
return; |
|
} |
|
pos1 += strlen (CERTIFICATE_HEADER); |
|
pos2 -= pos1; |
|
std::string base64 = cert.substr (pos1, pos2); |
|
|
|
CryptoPP::ByteQueue queue; |
|
CryptoPP::Base64Decoder decoder; // regular base64 rather than I2P |
|
decoder.Attach (new CryptoPP::Redirector (queue)); |
|
decoder.Put ((const uint8_t *)base64.data(), base64.length()); |
|
decoder.MessageEnd (); |
|
|
|
// extract X.509 |
|
CryptoPP::BERSequenceDecoder x509Cert (queue); |
|
CryptoPP::BERSequenceDecoder tbsCert (x509Cert); |
|
// version |
|
uint32_t ver; |
|
CryptoPP::BERGeneralDecoder context (tbsCert, CryptoPP::CONTEXT_SPECIFIC | CryptoPP::CONSTRUCTED); |
|
CryptoPP::BERDecodeUnsigned<uint32_t>(context, ver, CryptoPP::INTEGER); |
|
// serial |
|
CryptoPP::Integer serial; |
|
serial.BERDecode(tbsCert); |
|
// signature |
|
CryptoPP::BERSequenceDecoder signature (tbsCert); |
|
signature.SkipAll(); |
|
|
|
// issuer |
|
std::string name; |
|
CryptoPP::BERSequenceDecoder issuer (tbsCert); |
|
{ |
|
CryptoPP::BERSetDecoder c (issuer); c.SkipAll(); |
|
CryptoPP::BERSetDecoder st (issuer); st.SkipAll(); |
|
CryptoPP::BERSetDecoder l (issuer); l.SkipAll(); |
|
CryptoPP::BERSetDecoder o (issuer); o.SkipAll(); |
|
CryptoPP::BERSetDecoder ou (issuer); ou.SkipAll(); |
|
CryptoPP::BERSetDecoder cn (issuer); |
|
{ |
|
CryptoPP::BERSequenceDecoder attributes (cn); |
|
{ |
|
CryptoPP::BERGeneralDecoder ident(attributes, CryptoPP::OBJECT_IDENTIFIER); |
|
ident.SkipAll (); |
|
CryptoPP::BERDecodeTextString (attributes, name, CryptoPP::UTF8_STRING); |
|
} |
|
} |
|
} |
|
issuer.SkipAll(); |
|
// validity |
|
CryptoPP::BERSequenceDecoder validity (tbsCert); |
|
validity.SkipAll(); |
|
// subject |
|
CryptoPP::BERSequenceDecoder subject (tbsCert); |
|
subject.SkipAll(); |
|
// public key |
|
CryptoPP::BERSequenceDecoder publicKey (tbsCert); |
|
{ |
|
CryptoPP::BERSequenceDecoder ident (publicKey); |
|
ident.SkipAll (); |
|
CryptoPP::BERGeneralDecoder key (publicKey, CryptoPP::BIT_STRING); |
|
key.Skip (1); // FIXME: probably bug in crypto++ |
|
CryptoPP::BERSequenceDecoder keyPair (key); |
|
CryptoPP::Integer n; |
|
n.BERDecode (keyPair); |
|
if (name.length () > 0) |
|
{ |
|
PublicKey value; |
|
n.Encode (value, 512); |
|
m_SigningKeys[name] = value; |
|
} |
|
else |
|
LogPrint (eLogWarning, "Unknown issuer. Skipped"); |
|
} |
|
publicKey.SkipAll(); |
|
|
|
tbsCert.SkipAll(); |
|
x509Cert.SkipAll(); |
|
} |
|
else |
|
LogPrint (eLogError, "Can't open certificate file ", filename); |
|
} |
|
|
|
void Reseeder::LoadCertificates () |
|
{ |
|
boost::filesystem::path reseedDir = i2p::util::filesystem::GetCertificatesDir() / "reseed"; |
|
|
|
if (!boost::filesystem::exists (reseedDir)) |
|
{ |
|
LogPrint (eLogWarning, "Reseed certificates not loaded. ", reseedDir, " doesn't exist"); |
|
return; |
|
} |
|
|
|
int numCertificates = 0; |
|
boost::filesystem::directory_iterator end; // empty |
|
for (boost::filesystem::directory_iterator it (reseedDir); it != end; ++it) |
|
{ |
|
if (boost::filesystem::is_regular_file (it->status()) && it->path ().extension () == ".crt") |
|
{ |
|
LoadCertificate (it->path ().string ()); |
|
numCertificates++; |
|
} |
|
} |
|
LogPrint (eLogInfo, numCertificates, " certificates loaded"); |
|
} |
|
|
|
} |
|
} |
|
|
|
|