mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2024-11-17 21:26:04 +00:00
934 lines
36 KiB
C++
934 lines
36 KiB
C++
#include <string.h>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <boost/regex.hpp>
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <cryptopp/asn.h>
|
|
#include <cryptopp/base64.h>
|
|
#include <cryptopp/crc.h>
|
|
#include <cryptopp/hmac.h>
|
|
#include <cryptopp/zinflate.h>
|
|
#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
|
|
#include <cryptopp/arc4.h>
|
|
#include "util/I2PEndian.h"
|
|
#include "Reseed.h"
|
|
#include "util/Log.h"
|
|
#include "Identity.h"
|
|
#include "crypto/CryptoConst.h"
|
|
#include "crypto/Signature.h"
|
|
#include "NetworkDatabase.h"
|
|
#include "util/util.h"
|
|
|
|
|
|
namespace i2p
|
|
{
|
|
namespace data
|
|
{
|
|
|
|
static std::vector<std::string> httpReseedHostList = {
|
|
"http://netdb.i2p2.no/", // only SU3 (v2) support
|
|
"http://i2p-netdb.innovatio.no/",
|
|
"http://193.150.121.66/netDb/"
|
|
};
|
|
|
|
static std::vector<std::string> httpsReseedHostList = {
|
|
// "https://193.150.121.66/netDb/", // unstable
|
|
// "https://i2p-netdb.innovatio.no/",// Vuln to POODLE
|
|
"https://netdb.i2p2.no/", // Only SU3 (v2) support
|
|
"https://reseed.i2p-projekt.de/", // Only HTTPS
|
|
//"https://cowpuncher.drollette.com/netdb/", // returns error
|
|
"https://netdb.rows.io:444/",
|
|
"https://uk.reseed.i2p2.no:444/"
|
|
// following hosts are fine but don't support AES256
|
|
/*"https://i2p.mooo.com/netDb/",
|
|
"https://link.mx24.eu/", // Only HTTPS and SU3 (v2) support
|
|
"https://i2pseed.zarrenspry.info/", // Only HTTPS and SU3 (v2) support
|
|
"https://ieb9oopo.mooo.com/" // Only HTTPS and SU3 (v2) support*/
|
|
};
|
|
|
|
Reseeder::Reseeder()
|
|
{
|
|
}
|
|
|
|
Reseeder::~Reseeder()
|
|
{
|
|
}
|
|
|
|
bool Reseeder::reseedNow()
|
|
{
|
|
// This method is deprecated
|
|
try
|
|
{
|
|
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 + httpsReseedHostList.size () - 1);
|
|
std::string reseedHost = (ind < httpReseedHostList.size()) ? httpReseedHostList[ind] :
|
|
httpsReseedHostList[ind - httpReseedHostList.size()];
|
|
return ReseedFromSU3 (reseedHost, ind >= httpReseedHostList.size());
|
|
}
|
|
|
|
int Reseeder::ReseedFromSU3 (const std::string& host, bool https)
|
|
{
|
|
std::string url = host + "i2pseeds.su3";
|
|
LogPrint (eLogInfo, "Dowloading SU3 from ", host);
|
|
std::string su3 = https ? HttpsRequest (url) : 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 uint32_t ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE = 0x02014B50;
|
|
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)
|
|
{
|
|
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);
|
|
// RSA-raw
|
|
i2p::crypto::RSASHA5124096RawVerifier verifier(it->second);
|
|
verifier.Update (tbs, tbsLen);
|
|
if (!verifier.Verify (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 (4, std::ios::cur); // skip fields we don't care about
|
|
uint32_t compressedSize, uncompressedSize;
|
|
uint8_t crc32[4];
|
|
s.read ((char *)crc32, 4);
|
|
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.read ((char *)crc32, 4);
|
|
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);
|
|
if (CryptoPP::CRC32().VerifyDigest (crc32, uncompressed, uncompressedSize))
|
|
{
|
|
i2p::data::netdb.AddRouterInfo (uncompressed, uncompressedSize);
|
|
numFiles++;
|
|
}
|
|
else
|
|
LogPrint (eLogError, "CRC32 verification failed");
|
|
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
|
|
{
|
|
if (signature != ZIP_CENTRAL_DIRECTORY_HEADER_SIGNATURE)
|
|
LogPrint (eLogWarning, "Missing zip central directory header");
|
|
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 false;
|
|
}
|
|
|
|
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 ();
|
|
|
|
LoadCertificate (queue);
|
|
}
|
|
else
|
|
LogPrint (eLogError, "Can't open certificate file ", filename);
|
|
}
|
|
|
|
std::string Reseeder::LoadCertificate (CryptoPP::ByteQueue& queue)
|
|
{
|
|
// 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();
|
|
return name;
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
std::string Reseeder::HttpsRequest (const std::string& address)
|
|
{
|
|
i2p::util::http::url u(address);
|
|
if (u.port_ == 80) u.port_ = 443;
|
|
TlsSession session (u.host_, u.port_);
|
|
|
|
if (session.IsEstablished ())
|
|
{
|
|
// send request
|
|
std::stringstream ss;
|
|
ss << "GET " << u.path_ << " HTTP/1.1\r\nHost: " << u.host_
|
|
<< "\r\nAccept: */*\r\n" << "User-Agent: Wget/1.11.4\r\n" << "Connection: close\r\n\r\n";
|
|
session.Send ((uint8_t *)ss.str ().c_str (), ss.str ().length ());
|
|
|
|
// read response
|
|
std::stringstream rs;
|
|
while (session.Receive (rs))
|
|
;
|
|
return i2p::util::http::GetHttpContent (rs);
|
|
}
|
|
else
|
|
return "";
|
|
}
|
|
|
|
//-------------------------------------------------------------
|
|
|
|
template<class Hash>
|
|
class TlsCipherMAC: public TlsCipher
|
|
{
|
|
public:
|
|
|
|
TlsCipherMAC (const uint8_t * keys): m_Seqn (0)
|
|
{
|
|
memcpy (m_MacKey, keys, Hash::DIGESTSIZE);
|
|
}
|
|
|
|
void CalculateMAC (uint8_t type, const uint8_t * buf, size_t len, uint8_t * mac)
|
|
{
|
|
uint8_t header[13]; // seqn (8) + type (1) + version (2) + length (2)
|
|
htobe64buf (header, m_Seqn);
|
|
header[8] = type; header[9] = 3; header[10] = 3; // 3,3 means TLS 1.2
|
|
htobe16buf (header + 11, len);
|
|
CryptoPP::HMAC<Hash> hmac (m_MacKey, Hash::DIGESTSIZE);
|
|
hmac.Update (header, 13);
|
|
hmac.Update (buf, len);
|
|
hmac.Final (mac);
|
|
m_Seqn++;
|
|
}
|
|
|
|
private:
|
|
|
|
uint64_t m_Seqn;
|
|
uint8_t m_MacKey[Hash::DIGESTSIZE]; // client
|
|
};
|
|
|
|
template<class Hash>
|
|
class TlsCipher_AES_256_CBC: public TlsCipherMAC<Hash>
|
|
{
|
|
public:
|
|
|
|
TlsCipher_AES_256_CBC (const uint8_t * keys): TlsCipherMAC<Hash> (keys)
|
|
{
|
|
m_Encryption.SetKey (keys + 2*Hash::DIGESTSIZE);
|
|
m_Decryption.SetKey (keys + 2*Hash::DIGESTSIZE + 32);
|
|
}
|
|
|
|
size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out)
|
|
{
|
|
size_t size = 0;
|
|
m_Rnd.GenerateBlock (out, 16); // iv
|
|
size += 16;
|
|
m_Encryption.SetIV (out);
|
|
memcpy (out + size, in, len);
|
|
size += len;
|
|
memcpy (out + size, mac, Hash::DIGESTSIZE);
|
|
size += Hash::DIGESTSIZE;
|
|
uint8_t paddingSize = size + 1;
|
|
paddingSize &= 0x0F; // %16
|
|
if (paddingSize > 0) paddingSize = 16 - paddingSize;
|
|
memset (out + size, paddingSize, paddingSize + 1); // paddind and last byte are equal to padding size
|
|
size += paddingSize + 1;
|
|
m_Encryption.Encrypt (out + 16, size - 16, out + 16);
|
|
return size;
|
|
}
|
|
|
|
size_t Decrypt (uint8_t * buf, size_t len) // payload is buf + 16
|
|
{
|
|
m_Decryption.SetIV (buf);
|
|
m_Decryption.Decrypt (buf + 16, len - 16, buf + 16);
|
|
return len - 16 - Hash::DIGESTSIZE - buf[len -1] - 1; // IV(16), mac(32 or 20) and padding
|
|
}
|
|
|
|
size_t GetIVSize () const { return 16; };
|
|
|
|
private:
|
|
|
|
CryptoPP::AutoSeededRandomPool m_Rnd;
|
|
i2p::crypto::CBCEncryption m_Encryption;
|
|
i2p::crypto::CBCDecryption m_Decryption;
|
|
};
|
|
|
|
|
|
class TlsCipher_RC4_SHA: public TlsCipherMAC<CryptoPP::SHA1>
|
|
{
|
|
public:
|
|
|
|
TlsCipher_RC4_SHA (const uint8_t * keys): TlsCipherMAC (keys)
|
|
{
|
|
m_Encryption.SetKey (keys + 40, 16); // 20 + 20
|
|
m_Decryption.SetKey (keys + 56, 16); // 20 + 20 + 16
|
|
}
|
|
|
|
size_t Encrypt (const uint8_t * in, size_t len, const uint8_t * mac, uint8_t * out)
|
|
{
|
|
memcpy (out, in, len);
|
|
memcpy (out + len, mac, 20);
|
|
m_Encryption.ProcessData (out, out, len + 20);
|
|
return len + 20;
|
|
}
|
|
|
|
size_t Decrypt (uint8_t * buf, size_t len)
|
|
{
|
|
m_Decryption.ProcessData (buf, buf, len);
|
|
return len - 20;
|
|
}
|
|
|
|
private:
|
|
|
|
CryptoPP::Weak1::ARC4 m_Encryption, m_Decryption;
|
|
};
|
|
|
|
|
|
TlsSession::TlsSession (const std::string& host, int port):
|
|
m_IsEstablished (false), m_Cipher (nullptr)
|
|
{
|
|
m_Site.connect(host, boost::lexical_cast<std::string>(port));
|
|
if (m_Site.good ())
|
|
Handshake ();
|
|
else
|
|
LogPrint (eLogError, "Can't connect to ", host, ":", port);
|
|
}
|
|
|
|
TlsSession::~TlsSession ()
|
|
{
|
|
delete m_Cipher;
|
|
}
|
|
|
|
void TlsSession::Handshake ()
|
|
{
|
|
static uint8_t clientHello[] =
|
|
{
|
|
0x16, // handshake
|
|
0x03, 0x03, // version (TLS 1.2)
|
|
0x00, 0x33, // length of handshake
|
|
// handshake
|
|
0x01, // handshake type (client hello)
|
|
0x00, 0x00, 0x2F, // length of handshake payload
|
|
// client hello
|
|
0x03, 0x03, // highest version supported (TLS 1.2)
|
|
0x45, 0xFA, 0x01, 0x19, 0x74, 0x55, 0x18, 0x36,
|
|
0x42, 0x05, 0xC1, 0xDD, 0x4A, 0x21, 0x80, 0x80,
|
|
0xEC, 0x37, 0x11, 0x93, 0x16, 0xF4, 0x66, 0x00,
|
|
0x12, 0x67, 0xAB, 0xBA, 0xFF, 0x29, 0x13, 0x9E, // 32 random bytes
|
|
0x00, // session id length
|
|
0x00, 0x06, // chiper suites length
|
|
0x00, 0x3D, // RSA_WITH_AES_256_CBC_SHA256
|
|
0x00, 0x35, // RSA_WITH_AES_256_CBC_SHA
|
|
0x00, 0x05, // RSA_WITH_RC4_128_SHA
|
|
0x01, // compression methods length
|
|
0x00, // no compression
|
|
0x00, 0x00 // extensions length
|
|
};
|
|
|
|
static uint8_t changeCipherSpecs[] =
|
|
{
|
|
0x14, // change cipher specs
|
|
0x03, 0x03, // version (TLS 1.2)
|
|
0x00, 0x01, // length
|
|
0x01 // type
|
|
};
|
|
|
|
// send ClientHello
|
|
m_Site.write ((char *)clientHello, sizeof (clientHello));
|
|
m_FinishedHash.Update (clientHello + 5, sizeof (clientHello) - 5);
|
|
// read ServerHello
|
|
uint8_t type;
|
|
m_Site.read ((char *)&type, 1);
|
|
uint16_t version;
|
|
m_Site.read ((char *)&version, 2);
|
|
uint16_t length;
|
|
m_Site.read ((char *)&length, 2);
|
|
length = be16toh (length);
|
|
char * serverHello = new char[length];
|
|
m_Site.read (serverHello, length);
|
|
m_FinishedHash.Update ((uint8_t *)serverHello, length);
|
|
uint8_t serverRandom[32];
|
|
if (serverHello[0] == 0x02) // handshake type server hello
|
|
memcpy (serverRandom, serverHello + 6, 32);
|
|
else
|
|
LogPrint (eLogError, "Unexpected handshake type ", (int)serverHello[0]);
|
|
uint8_t sessionIDLen = serverHello[38]; // 6 + 32
|
|
char * cipherSuite = serverHello + 39 + sessionIDLen;
|
|
if (cipherSuite[1] == 0x3D || cipherSuite[1] == 0x35 || cipherSuite[1] == 0x05)
|
|
m_IsEstablished = true;
|
|
else
|
|
LogPrint (eLogError, "Unsupported cipher ", (int)cipherSuite[0], ",", (int)cipherSuite[1]);
|
|
// read Certificate
|
|
m_Site.read ((char *)&type, 1);
|
|
m_Site.read ((char *)&version, 2);
|
|
m_Site.read ((char *)&length, 2);
|
|
length = be16toh (length);
|
|
char * certificate = new char[length];
|
|
m_Site.read (certificate, length);
|
|
m_FinishedHash.Update ((uint8_t *)certificate, length);
|
|
CryptoPP::RSA::PublicKey publicKey;
|
|
// 0 - handshake type
|
|
// 1 - 3 - handshake payload length
|
|
// 4 - 6 - length of array of certificates
|
|
// 7 - 9 - length of certificate
|
|
if (certificate[0] == 0x0B) // handshake type certificate
|
|
publicKey = ExtractPublicKey ((uint8_t *)certificate + 10, length - 10);
|
|
else
|
|
LogPrint (eLogError, "Unexpected handshake type ", (int)certificate[0]);
|
|
// read ServerHelloDone
|
|
m_Site.read ((char *)&type, 1);
|
|
m_Site.read ((char *)&version, 2);
|
|
m_Site.read ((char *)&length, 2);
|
|
length = be16toh (length);
|
|
char * serverHelloDone = new char[length];
|
|
m_Site.read (serverHelloDone, length);
|
|
m_FinishedHash.Update ((uint8_t *)serverHelloDone, length);
|
|
if (serverHelloDone[0] != 0x0E) // handshake type hello done
|
|
LogPrint (eLogError, "Unexpected handshake type ", (int)serverHelloDone[0]);
|
|
// our turn now
|
|
// generate secret key
|
|
uint8_t secret[48];
|
|
secret[0] = 3; secret[1] = 3; // version
|
|
CryptoPP::AutoSeededRandomPool rnd;
|
|
rnd.GenerateBlock (secret + 2, 46); // 46 random bytes
|
|
// encrypt RSA
|
|
CryptoPP::RSAES_PKCS1v15_Encryptor encryptor(publicKey);
|
|
size_t encryptedLen = encryptor.CiphertextLength (48); // number of bytes for encrypted 48 bytes, usually 256 (2048 bits key)
|
|
uint8_t * encrypted = new uint8_t[encryptedLen + 2]; // + 2 bytes for length
|
|
htobe16buf (encrypted, encryptedLen); // first two bytes means length
|
|
encryptor.Encrypt (rnd, secret, 48, encrypted + 2);
|
|
// send ClientKeyExchange
|
|
// 0x10 - handshake type "client key exchange"
|
|
SendHandshakeMsg (0x10, encrypted, encryptedLen + 2);
|
|
delete[] encrypted;
|
|
// send ChangeCipherSpecs
|
|
m_Site.write ((char *)changeCipherSpecs, sizeof (changeCipherSpecs));
|
|
// calculate master secret
|
|
uint8_t random[64];
|
|
memcpy (random, clientHello + 11, 32);
|
|
memcpy (random + 32, serverRandom, 32);
|
|
PRF (secret, "master secret", random, 64, 48, m_MasterSecret);
|
|
// create keys
|
|
memcpy (random, serverRandom, 32);
|
|
memcpy (random + 32, clientHello + 11, 32);
|
|
uint8_t keys[128]; // clientMACKey(32 or 20), serverMACKey(32 or 20), clientKey(32), serverKey(32)
|
|
PRF (m_MasterSecret, "key expansion", random, 64, 128, keys);
|
|
// create cipher
|
|
if (cipherSuite[1] == 0x3D)
|
|
{
|
|
LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA256");
|
|
m_Cipher = new TlsCipher_AES_256_CBC<CryptoPP::SHA256> (keys);
|
|
}
|
|
else if (cipherSuite[1] == 0x35)
|
|
{
|
|
LogPrint (eLogInfo, "Chiper suite is RSA_WITH_AES_256_CBC_SHA");
|
|
m_Cipher = new TlsCipher_AES_256_CBC<CryptoPP::SHA1> (keys);
|
|
}
|
|
else
|
|
{
|
|
// TODO:
|
|
if (cipherSuite[1] == 0x05)
|
|
LogPrint (eLogInfo, "Chiper suite is RSA_WITH_RC4_128_SHA");
|
|
m_Cipher = new TlsCipher_RC4_SHA (keys);
|
|
}
|
|
// send finished
|
|
SendFinishedMsg ();
|
|
// read ChangeCipherSpecs
|
|
uint8_t changeCipherSpecs1[6];
|
|
m_Site.read ((char *)changeCipherSpecs1, 6);
|
|
// read finished
|
|
m_Site.read ((char *)&type, 1);
|
|
m_Site.read ((char *)&version, 2);
|
|
m_Site.read ((char *)&length, 2);
|
|
length = be16toh (length);
|
|
char * finished1 = new char[length];
|
|
m_Site.read (finished1, length);
|
|
m_Cipher->Decrypt ((uint8_t *)finished1, length); // for streaming ciphers
|
|
delete[] finished1;
|
|
|
|
delete[] serverHello;
|
|
delete[] certificate;
|
|
delete[] serverHelloDone;
|
|
}
|
|
|
|
void TlsSession::SendHandshakeMsg (uint8_t handshakeType, uint8_t * data, size_t len)
|
|
{
|
|
uint8_t handshakeHeader[9];
|
|
handshakeHeader[0] = 0x16; // handshake
|
|
handshakeHeader[1] = 0x03; handshakeHeader[2] = 0x03; // version is always TLS 1.2 (3,3)
|
|
htobe16buf (handshakeHeader + 3, len + 4); // length of payload
|
|
//payload starts
|
|
handshakeHeader[5] = handshakeType; // handshake type
|
|
handshakeHeader[6] = 0; // highest byte of payload length is always zero
|
|
htobe16buf (handshakeHeader + 7, len); // length of data
|
|
m_Site.write ((char *)handshakeHeader, 9);
|
|
m_FinishedHash.Update (handshakeHeader + 5, 4); // only payload counts
|
|
m_Site.write ((char *)data, len);
|
|
m_FinishedHash.Update (data, len);
|
|
}
|
|
|
|
void TlsSession::SendFinishedMsg ()
|
|
{
|
|
// 0x16 handshake
|
|
// 0x03, 0x03 version (TLS 1.2)
|
|
// 2 bytes length of handshake (80 or 64 bytes)
|
|
// handshake (encrypted)
|
|
// unencrypted context
|
|
// 0x14 handshake type (finished)
|
|
// 0x00, 0x00, 0x0C length of handshake payload
|
|
// 12 bytes of verified data
|
|
|
|
uint8_t finishedHashDigest[32], finishedPayload[40], encryptedPayload[80];
|
|
finishedPayload[0] = 0x14; // handshake type (finished)
|
|
finishedPayload[1] = 0; finishedPayload[2] = 0; finishedPayload[3] = 0x0C; // 12 bytes
|
|
m_FinishedHash.Final (finishedHashDigest);
|
|
PRF (m_MasterSecret, "client finished", finishedHashDigest, 32, 12, finishedPayload + 4);
|
|
uint8_t mac[32];
|
|
m_Cipher->CalculateMAC (0x16, finishedPayload, 16, mac);
|
|
size_t encryptedPayloadSize = m_Cipher->Encrypt (finishedPayload, 16, mac, encryptedPayload);
|
|
uint8_t finished[5];
|
|
finished[0] = 0x16; // handshake
|
|
finished[1] = 0x03; finished[2] = 0x03; // version is always TLS 1.2 (3,3)
|
|
htobe16buf (finished + 3, encryptedPayloadSize); // length of payload
|
|
m_Site.write ((char *)finished, sizeof (finished));
|
|
m_Site.write ((char *)encryptedPayload, encryptedPayloadSize);
|
|
}
|
|
|
|
void TlsSession::PRF (const uint8_t * secret, const char * label, const uint8_t * random, size_t randomLen,
|
|
size_t len, uint8_t * buf)
|
|
{
|
|
// secret is assumed 48 bytes
|
|
// random is not more than 64 bytes
|
|
CryptoPP::HMAC<CryptoPP::SHA256> hmac (secret, 48);
|
|
uint8_t seed[96]; size_t seedLen;
|
|
seedLen = strlen (label);
|
|
memcpy (seed, label, seedLen);
|
|
memcpy (seed + seedLen, random, randomLen);
|
|
seedLen += randomLen;
|
|
|
|
size_t offset = 0;
|
|
uint8_t a[128];
|
|
hmac.CalculateDigest (a, seed, seedLen);
|
|
while (offset < len)
|
|
{
|
|
memcpy (a + 32, seed, seedLen);
|
|
hmac.CalculateDigest (buf + offset, a, seedLen + 32);
|
|
offset += 32;
|
|
hmac.CalculateDigest (a, a, 32);
|
|
}
|
|
}
|
|
|
|
CryptoPP::RSA::PublicKey TlsSession::ExtractPublicKey (const uint8_t * certificate, size_t len)
|
|
{
|
|
CryptoPP::ByteQueue queue;
|
|
queue.Put (certificate, len);
|
|
queue.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
|
|
CryptoPP::BERSequenceDecoder issuer (tbsCert);
|
|
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, e;
|
|
n.BERDecode (keyPair);
|
|
e.BERDecode (keyPair);
|
|
|
|
CryptoPP::RSA::PublicKey ret;
|
|
ret.Initialize (n, e);
|
|
return ret;
|
|
}
|
|
|
|
void TlsSession::Send (const uint8_t * buf, size_t len)
|
|
{
|
|
uint8_t * out = new uint8_t[len + 64 + 5]; // 64 = 32 mac + 16 iv + upto 16 padding, 5 = header
|
|
out[0] = 0x17; // application data
|
|
out[1] = 0x03; out[2] = 0x03; // version
|
|
uint8_t mac[32];
|
|
m_Cipher->CalculateMAC (0x17, buf, len, mac);
|
|
size_t encryptedLen = m_Cipher->Encrypt (buf, len, mac, out + 5);
|
|
htobe16buf (out + 3, encryptedLen);
|
|
m_Site.write ((char *)out, encryptedLen + 5);
|
|
delete[] out;
|
|
}
|
|
|
|
bool TlsSession::Receive (std::ostream& rs)
|
|
{
|
|
if (m_Site.eof ()) return false;
|
|
uint8_t type; uint16_t version, length;
|
|
m_Site.read ((char *)&type, 1);
|
|
m_Site.read ((char *)&version, 2);
|
|
m_Site.read ((char *)&length, 2);
|
|
length = be16toh (length);
|
|
uint8_t * buf = new uint8_t[length];
|
|
m_Site.read ((char *)buf, length);
|
|
size_t decryptedLen = m_Cipher->Decrypt (buf, length);
|
|
rs.write ((char *)buf + m_Cipher->GetIVSize (), decryptedLen);
|
|
delete[] buf;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|