diff --git a/Config.cpp b/Config.cpp index 8e212360..81c2a2ce 100644 --- a/Config.cpp +++ b/Config.cpp @@ -113,7 +113,8 @@ namespace config { ("log", value()->default_value(""), "Logs destination: stdout, file (stdout if not set, file - otherwise, for compatibility)") ("logfile", value()->default_value(""), "Path to logfile (stdout if not set, autodetect if daemon)") ("loglevel", value()->default_value("info"), "Set the minimal level of log messages (debug, info, warn, error)") - ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") + ("family", value()->default_value(""), "Specify a family, router belongs to") + ("datadir", value()->default_value(""), "Path to storage of i2pd data (RI, keys, peer profiles, ...)") ("host", value()->default_value("0.0.0.0"), "External IP") ("port", value()->default_value(0), "Port to listen for incoming connections (default: auto)") ("ipv6", value()->zero_tokens()->default_value(false), "Enable communication through ipv6") diff --git a/Daemon.cpp b/Daemon.cpp index 242f4bbf..0687d0b9 100644 --- a/Daemon.cpp +++ b/Daemon.cpp @@ -120,7 +120,7 @@ namespace i2p i2p::context.SetHighBandwidth (); else i2p::context.SetLowBandwidth (); - } + } else if (isFloodfill) { LogPrint(eLogInfo, "Daemon: floodfill bandwidth set to 'extra'"); @@ -132,6 +132,11 @@ namespace i2p i2p::context.SetLowBandwidth (); } + std::string family; i2p::config::GetOption("family", family); + i2p::context.SetFamily (family); + if (family.length () > 0) + LogPrint(eLogInfo, "Daemon: family set to ", family); + return true; } diff --git a/Family.cpp b/Family.cpp index 5470a92c..d83adf2a 100644 --- a/Family.cpp +++ b/Family.cpp @@ -1,7 +1,9 @@ +#include #include "util.h" #include #include #include "Log.h" +#include "Crypto.h" #include "Family.h" namespace i2p @@ -24,12 +26,19 @@ namespace data { SSL * ssl = SSL_new (ctx); X509 * cert = SSL_get_certificate (ssl); - // verify if (cert) { + std::shared_ptr verifier; // extract issuer name char name[100]; X509_NAME_oneline (X509_get_issuer_name(cert), name, 100); + char * cn = strstr (name, "CN="); + if (cn) + { + cn += 3; + char * family = strstr (cn, ".family"); + if (family) family[0] = 0; + } auto pkey = X509_get_pubkey (cert); int keyType = EVP_PKEY_type(pkey->type); switch (keyType) @@ -39,13 +48,37 @@ namespace data break; case EVP_PKEY_EC: { - //EC_KEY * ecKey = EVP_PKEY_get0_EC_KEY (pkey); + EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey); + if (ecKey) + { + auto group = EC_KEY_get0_group (ecKey); + if (group) + { + int curve = EC_GROUP_get_curve_name (group); + if (curve == NID_X9_62_prime256v1) + { + uint8_t signingKey[64]; + BIGNUM * x = BN_new(), * y = BN_new(); + EC_POINT_get_affine_coordinates_GFp (group, + EC_KEY_get0_public_key (ecKey), x, y, NULL); + i2p::crypto::bn2buf (x, signingKey, 32); + i2p::crypto::bn2buf (y, signingKey + 32, 32); + BN_free (x); BN_free (y); + verifier = std::make_shared(signingKey); + } + else + LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); + } + EC_KEY_free (ecKey); + } break; } default: LogPrint (eLogWarning, "Family: Certificate key type ", keyType, " is not supported"); } EVP_PKEY_free (pkey); + if (verifier && cn) + m_SigningKeys[cn] = verifier; } SSL_free (ssl); } @@ -72,6 +105,68 @@ namespace data if (numCertificates > 0) LogPrint (eLogInfo, "Family: ", numCertificates, " certificates loaded"); } + + bool Families::VerifyFamily (const std::string& family, const IdentHash& ident, + const char * signature, const char * key) + { + uint8_t buf[50], signatureBuf[64]; + size_t len = family.length (), signatureLen = strlen (signature); + memcpy (buf, family.c_str (), len); + memcpy (buf + len, (const uint8_t *)ident, 32); + len += 32; + Base64ToByteStream (signature, signatureLen, signatureBuf, 64); + auto it = m_SigningKeys.find (family); + if (it != m_SigningKeys.end ()) + return it->second->Verify (buf, len, signatureBuf); + // TODO: process key + return true; + } + + std::string CreateFamilySignature (const std::string& family, const IdentHash& ident) + { + std::string sig; + auto filename = i2p::util::filesystem::GetDefaultDataDir() / "family" / (family + ".key"); + SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ()); + int ret = SSL_CTX_use_PrivateKey_file (ctx, filename.string ().c_str (), SSL_FILETYPE_PEM); + if (ret) + { + SSL * ssl = SSL_new (ctx); + EVP_PKEY * pkey = SSL_get_privatekey (ssl); + EC_KEY * ecKey = EVP_PKEY_get1_EC_KEY (pkey); + if (ecKey) + { + auto group = EC_KEY_get0_group (ecKey); + if (group) + { + int curve = EC_GROUP_get_curve_name (group); + if (curve == NID_X9_62_prime256v1) + { + uint8_t signingPrivateKey[32], buf[50], signature[64]; + i2p::crypto::bn2buf (EC_KEY_get0_private_key (ecKey), signingPrivateKey, 32); + i2p::crypto::ECDSAP256Signer signer (signingPrivateKey); + size_t len = family.length (); + memcpy (buf, family.c_str (), len); + memcpy (buf + len, (const uint8_t *)ident, 32); + len += 32; + signer.Sign (buf, len, signature); + len = Base64EncodingBufferSize (64); + char * b64 = new char[len+1]; + len = ByteStreamToBase64 (signature, 64, b64, len); + b64[len] = 0; + sig = b64; + delete[] b64; + } + else + LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); + } + } + SSL_free (ssl); + } + else + LogPrint (eLogError, "Family: Can't open keys file ", filename.string ()); + SSL_CTX_free (ctx); + return sig; + } } } diff --git a/Family.h b/Family.h index ca8dac3f..42a37292 100644 --- a/Family.h +++ b/Family.h @@ -1,10 +1,11 @@ #ifndef FAMILY_H__ -#define FAMILY_H_ +#define FAMILY_H__ #include #include #include #include "Signature.h" +#include "Identity.h" namespace i2p { @@ -17,6 +18,8 @@ namespace data Families (); ~Families (); void LoadCertificates (); + bool VerifyFamily (const std::string& family, const IdentHash& ident, + const char * signature, const char * key = nullptr); private: @@ -26,6 +29,9 @@ namespace data std::map > m_SigningKeys; }; + + std::string CreateFamilySignature (const std::string& family, const IdentHash& ident); + // return base64 signature of empty string in case of failure } } diff --git a/NetDb.h b/NetDb.h index 33fd6e27..1ec2ade5 100644 --- a/NetDb.h +++ b/NetDb.h @@ -62,6 +62,7 @@ namespace data void PostI2NPMsg (std::shared_ptr msg); void Reseed (); + Families& GetFamilies () { return m_Families; }; // for web interface int GetNumRouters () const { return m_RouterInfos.size (); }; diff --git a/RouterContext.cpp b/RouterContext.cpp index 90961948..070e52ad 100644 --- a/RouterContext.cpp +++ b/RouterContext.cpp @@ -8,6 +8,7 @@ #include "util.h" #include "version.h" #include "Log.h" +#include "Family.h" #include "RouterContext.h" namespace i2p @@ -141,12 +142,29 @@ namespace i2p { m_RouterInfo.SetCaps (m_RouterInfo.GetCaps () & ~i2p::data::RouterInfo::eFloodfill); // we don't publish number of routers and leaseset for non-floodfill - m_RouterInfo.DeleteProperty (ROUTER_INFO_PROPERTY_LEASESETS); - m_RouterInfo.DeleteProperty (ROUTER_INFO_PROPERTY_ROUTERS); + m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS); + m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS); } UpdateRouterInfo (); } + void RouterContext::SetFamily (const std::string& family) + { + std::string signature; + if (family.length () > 0) + signature = i2p::data::CreateFamilySignature (family, GetIdentHash ()); + if (signature.length () > 0) + { + m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY, family); + m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG, signature); + } + else + { + m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY); + m_RouterInfo.DeleteProperty (i2p::data::ROUTER_INFO_PROPERTY_FAMILY_SIG); + } + } + void RouterContext::SetHighBandwidth () { if (!m_RouterInfo.IsHighBandwidth () || m_RouterInfo.IsExtraBandwidth ()) @@ -284,8 +302,8 @@ namespace i2p if (m_IsFloodfill) { // update routers and leasesets - m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_LEASESETS, boost::lexical_cast(i2p::data::netdb.GetNumLeaseSets ())); - m_RouterInfo.SetProperty (ROUTER_INFO_PROPERTY_ROUTERS, boost::lexical_cast(i2p::data::netdb.GetNumRouters ())); + m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_LEASESETS, boost::lexical_cast(i2p::data::netdb.GetNumLeaseSets ())); + m_RouterInfo.SetProperty (i2p::data::ROUTER_INFO_PROPERTY_ROUTERS, boost::lexical_cast(i2p::data::netdb.GetNumRouters ())); UpdateRouterInfo (); } } diff --git a/RouterContext.h b/RouterContext.h index 8c07b17f..bc1ee836 100644 --- a/RouterContext.h +++ b/RouterContext.h @@ -15,9 +15,6 @@ namespace i2p const char ROUTER_INFO[] = "router.info"; const char ROUTER_KEYS[] = "router.keys"; const int ROUTER_INFO_UPDATE_INTERVAL = 1800; // 30 minutes - - const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets"; - const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters"; enum RouterStatus { @@ -60,6 +57,7 @@ namespace i2p void SetReachable (); bool IsFloodfill () const { return m_IsFloodfill; }; void SetFloodfill (bool floodfill); + void SetFamily (const std::string& family); void SetHighBandwidth (); void SetLowBandwidth (); void SetExtraBandwidth (); diff --git a/RouterInfo.cpp b/RouterInfo.cpp index 3267392b..1bf05a99 100644 --- a/RouterInfo.cpp +++ b/RouterInfo.cpp @@ -8,6 +8,7 @@ #include "Base.h" #include "Timestamp.h" #include "Log.h" +#include "NetDb.h" #include "RouterInfo.h" namespace i2p @@ -262,11 +263,26 @@ namespace data if (!strcmp (key, "caps")) ExtractCaps (value); // check netId - if (!strcmp (key, "netId") && atoi (value) != I2PD_NET_ID) + else if (!strcmp (key, ROUTER_INFO_PROPERTY_NETID) && atoi (value) != I2PD_NET_ID) { - LogPrint (eLogError, "Unexpected netid=", value); + LogPrint (eLogError, "Unexpected ", ROUTER_INFO_PROPERTY_NETID, "=", value); m_IsUnreachable = true; } + // family + else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY)) + { + m_Family = value; + boost::to_lower (m_Family); + } + else if (!strcmp (key, ROUTER_INFO_PROPERTY_FAMILY_SIG)) + { + if (!netdb.GetFamilies ().VerifyFamily (m_Family, GetIdentHash (), value)) + { + LogPrint (eLogWarning, "RouterInfo: family signature verification failed"); + m_Family.clear (); + } + } + if (!s) return; } diff --git a/RouterInfo.h b/RouterInfo.h index 4870eb50..5b77e170 100644 --- a/RouterInfo.h +++ b/RouterInfo.h @@ -14,6 +14,12 @@ namespace i2p { namespace data { + const char ROUTER_INFO_PROPERTY_LEASESETS[] = "netdb.knownLeaseSets"; + const char ROUTER_INFO_PROPERTY_ROUTERS[] = "netdb.knownRouters"; + const char ROUTER_INFO_PROPERTY_NETID[] = "netId"; + const char ROUTER_INFO_PROPERTY_FAMILY[] = "family"; + const char ROUTER_INFO_PROPERTY_FAMILY_SIG[] = "family.sig"; + const char CAPS_FLAG_FLOODFILL = 'f'; const char CAPS_FLAG_HIDDEN = 'H'; const char CAPS_FLAG_REACHABLE = 'R'; @@ -180,7 +186,7 @@ namespace data private: - std::string m_FullPath; + std::string m_FullPath, m_Family; std::shared_ptr m_RouterIdentity; uint8_t * m_Buffer; size_t m_BufferLen; diff --git a/contrib/certificates/family/i2pd-dev.crt b/contrib/certificates/family/i2pd-dev.crt new file mode 100644 index 00000000..3bb6f429 --- /dev/null +++ b/contrib/certificates/family/i2pd-dev.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB6TCCAY+gAwIBAgIJAI7G9MXxh7OjMAoGCCqGSM49BAMCMHoxCzAJBgNVBAYT +AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNVBAoMFUkyUCBBbm9u +eW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYDVQQDDBdpMnBkLWRl +di5mYW1pbHkuaTJwLm5ldDAeFw0xNjAyMjAxNDE2MzhaFw0yNjAyMTcxNDE2Mzha +MHoxCzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxHjAcBgNV +BAoMFUkyUCBBbm9ueW1vdXMgTmV0d29yazEPMA0GA1UECwwGZmFtaWx5MSAwHgYD +VQQDDBdpMnBkLWRldi5mYW1pbHkuaTJwLm5ldDBZMBMGByqGSM49AgEGCCqGSM49 +AwEHA0IABMlWL3loKVOfsA8Rm91QR53Il69mQiaB7n3rUhfPkJb9MYc1S4198azE +iSnNZSXicKDPIifaCgvONmbACzElHc8wCgYIKoZIzj0EAwIDSAAwRQIgYWmSFuai +TJvVrlB5RlbiiNFCEootjWP8BFM3t/yFeaQCIQDkg4xcQIRGTHhjrCsxmlz9KcRF +G+eIF+ATfI93nPseLw== +-----END CERTIFICATE----- diff --git a/docs/configuration.md b/docs/configuration.md index d5a3a5b0..79f95f10 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -21,6 +21,7 @@ Command line options * --notransit - Router will not accept transit tunnels at startup * --floodfill - Router will be floodfill * --bandwidth= - L if bandwidth is limited to 32Kbs/sec, O - to 256Kbs/sec, P - unlimited +* --family= - Name of a family, router belongs to * --svcctl= - Windows service management (--svcctl="install" or --svcctl="remove") * --http.address= - The address to listen on (HTTP server) diff --git a/docs/family.md b/docs/family.md new file mode 100644 index 00000000..0f307143 --- /dev/null +++ b/docs/family.md @@ -0,0 +1,32 @@ +Family configuration +==================== + +Your might want to specify a family, your router belongs to. +There are two possibilities: create new family or joing to existing. + +New family +----------- +You must create family self-signed certificate and key. +The only key type supposted is prime256v1. +Use the following list of commands: +openssl ecparam -name prime256v1 -genkey -out .key +openssl req -new -key .key -out .csr +touch v3.ext +openssl x509 -req -days 3650 -in .csr -signkey .key -out .crt -extfile v3.ext + +specify .family.i2p.net for CN. + +Once you are done with it place .key and .crt to /family folder (for exmple ~/.i2pd/family). +You should provide these two files to other members joining your family. +If you want to register you family and let I2P network recorgnize it, create pull request for you .crt file into contrib/certificate/family. +It will appear in i2pd and I2P next releases packages. Don't place .key file, it must be shared betwwen you family members only. + +Join existing family +-------------------- +Once you and that family agree to do it, they must give you .key and .crt file and you must place to /family folder. + +Publish your family +------------------ +Run i2pd with parameter 'family=', make sure you have .key and .crt in your 'family' folder. +If everything is set properly, you router.info will contain two new fields: 'family' and 'family.sig'. +