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.
932 lines
36 KiB
932 lines
36 KiB
#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 "NetDb.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; |
|
} |
|
} |
|
} |
|
|
|
|