diff --git a/.gitignore b/.gitignore index 8e93fe2..f31fffe 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,11 @@ # built binaries keygen keyinfo +famtool # private key files -*.dat \ No newline at end of file +*.dat +*.pem + +# certificates +*.crt \ No newline at end of file diff --git a/Makefile b/Makefile index d638628..2bea9ce 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ OBJECTS = $(SOURCES:.cpp=.o) I2PD_LIB = libi2pd.a -all: keygen keyinfo +all: keygen keyinfo famtool keygen: $(OBJECTS) $(CXX) -o keygen keygen.o $(LDFLAGS) $(LIBS) @@ -19,6 +19,9 @@ keygen: $(OBJECTS) keyinfo: $(OBJECTS) $(CXX) -o keyinfo keyinfo.o $(LDFLAGS) $(LIBS) +famtool: $(OBJECTS) + $(CXX) -o famtool famtool.o $(LDFLAGS) $(LIBS) + $(OBJECTS): libi2pd.a .SUFFIXES: diff --git a/famtool.cpp b/famtool.cpp new file mode 100644 index 0000000..d8cac59 --- /dev/null +++ b/famtool.cpp @@ -0,0 +1,374 @@ +/** + * famtool - a tool for creating and verifying router families + */ +#include +#include +#include +#include +#include "Crypto.h" +#include "RouterInfo.h" +#include "Base.h" +#include +#include +#include +#include +#include + +using namespace i2p::crypto; +using namespace i2p::data; + +static void usage(const std::string & name) +{ + std::cout << "usage: " << name << " [-h] [-v] [-g -n family -c family.crt -k family.pem] [-s -n family -k family.pem -i router.info] [-V -c family.crt -i router.info]" << std::endl; +} + +static void printhelp(const std::string & name) +{ + usage(name); + std::cout << std::endl; + std::cout << "generate a new family signing key for family called ``i2pfam''" << std::endl; + std::cout << name << " -g -n i2pfam -c myfam.crt -k myfam.pem" << std::endl << std::endl; + std::cout << "sign a router info with family signing key" << std::endl; + std::cout << name << " -s -n i2pfam -k myfam.pem -i router.info" << std::endl << std::endl; + std::cout << "verify signed router.info" << std::endl; + std::cout << name << " -V -c myfam.pem -i router.info" << std::endl << std::endl; +} + + +static std::shared_ptr LoadCertificate (const std::string& filename) +{ + std::shared_ptr verifier; + 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); + if (cert) + { + // 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) + { + case EVP_PKEY_EC: + { + 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); + bn2buf (x, signingKey, 32); + bn2buf (y, signingKey + 32, 32); + BN_free (x); BN_free (y); + verifier = std::make_shared(signingKey); + } + } + EC_KEY_free (ecKey); + } + } + + default: + break; + } + EVP_PKEY_free (pkey); + } + SSL_free (ssl); + } + SSL_CTX_free (ctx); + return verifier; +} + +static bool CreateFamilySignature (const std::string& family, const IdentHash& ident, const std::string & filename, std::string & sig) +{ + SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ()); + int ret = SSL_CTX_use_PrivateKey_file (ctx, filename.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]; + bn2buf (EC_KEY_get0_private_key (ecKey), signingPrivateKey, 32); + 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 + return false; + } + } + SSL_free (ssl); + } + else + return false; + SSL_CTX_free (ctx); + return true; +} + +int main(int argc, char * argv[]) +{ + if (argc == 1) { + usage(argv[0]); + return -1; + } + int opt; + bool verbose = false; + bool help = false; + bool gen = false; + bool sign = false; + bool verify = false; + std::string fam; + std::string privkey; + std::string certfile; + std::string infoFile; + while((opt = getopt(argc, argv, "vVhgsn:i:c:k:")) != -1) { + switch(opt) { + case 'v': + verbose = true; + break; + case 'h': + help = true; + break; + case 'g': + gen = true; + break; + case 'n': + fam = std::string(argv[optind-1]); + if (fam.size() + 32 > 50) { + std::cout << "family name too long" << std::endl; + return 1; + } + break; + case 'i': + infoFile = std::string(argv[optind-1]); + break; + case 'c': + certfile = std::string(argv[optind-1]); + break; + case 'k': + privkey = std::string(argv[optind-1]); + break; + case 'V': + verify = true; + break; + case 's': + sign = true; + break; + default: + usage(argv[0]); + return -1; + } + } + if(help) { + printhelp(argv[0]); + return 0; + } + InitCrypto(false); + + + + if(!fam.size()) { + // no family name + std::cout << "no family name specified" << std::endl; + return 1; + } + // generate family key code + if(gen) { + std::cout << "generate key for router family " << fam << "..." << std::endl; + if(!privkey.size()) privkey = fam + ".pem"; + if(!certfile.size()) certfile = fam + ".crt"; + + std::string cn = fam + ".family.i2p.net"; + + + FILE * privf = fopen(privkey.c_str(), "w"); + if(!privf) { + fprintf(stderr, "cannot open %s: %s\n", privkey.c_str(), strerror(errno)); + return 1; + } + + FILE * certf = fopen(certfile.c_str(), "w"); + if(!certf) { + fprintf(stderr, "cannot open %s: %s\n", certfile.c_str(), strerror(errno)); + return 1; + } + + // openssl fagmastery starts here + + EC_KEY * k_priv = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + assert(k_priv); + EC_KEY_set_asn1_flag(k_priv, OPENSSL_EC_NAMED_CURVE); + EC_KEY_generate_key(k_priv); + if(verbose) std::cout << "generated key" << std::endl; + // TODO: password protection + PEM_write_ECPrivateKey(privf, k_priv, nullptr, nullptr, 0, nullptr, nullptr); + fclose(privf); + if(verbose) std::cout << "wrote private key" << std::endl; + + + EVP_PKEY * ev_k = EVP_PKEY_new(); + assert(ev_k); + assert(EVP_PKEY_assign_EC_KEY(ev_k, k_priv) == 1); + + + // TODO: password protection + PEM_write_ECPrivateKey(privf, k_priv, nullptr, nullptr, 0, nullptr, nullptr); + + X509 * x = X509_new(); + assert(x); + + X509_set_version(x, 2); + ASN1_INTEGER_set(X509_get_serialNumber(x), 0); + X509_gmtime_adj(X509_get_notBefore(x),0); + // TODO: make expiration date configurable + X509_gmtime_adj(X509_get_notAfter(x),(long)60*60*24*365*10); + + X509_set_pubkey(x, ev_k); + + X509_NAME * name = X509_get_subject_name(x); + X509_NAME_add_entry_by_txt(name,"C", MBSTRING_ASC, (unsigned char *) "XX", -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"ST", MBSTRING_ASC, (unsigned char *) "XX", -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"L", MBSTRING_ASC, (unsigned char *) "XX", -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"O", MBSTRING_ASC, (unsigned char *) "I2P Anonymous Network", -1, -1, 0); + + X509_NAME_add_entry_by_txt(name,"OU", MBSTRING_ASC, (unsigned char *) "family", -1, -1, 0); + X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, (unsigned char *) cn.c_str(), -1, -1, 0); + X509_set_issuer_name(x,name); + + if(verbose) std::cout << "signing cert" << std::endl; + assert(X509_sign(x, ev_k, EVP_sha256())); + if(verbose) std::cout << "writing private key" << std::endl; + PEM_write_X509(certf, x); + + fclose(certf); + + EVP_PKEY_free(ev_k); + EC_KEY_free(k_priv); + X509_free(x); + } + + if (sign) { + // sign + if (!infoFile.size()) { + // no router info specififed + std::cerr << "no routerinfo file specified" << std::endl; + return 1; + } + if (!privkey.size()) { + // no private key specified + std::cerr << "no private key specififed" << std::endl; + return 1; + } + + { + std::ifstream i; + i.open(infoFile); + if(!i.is_open()) { + std::cout << "cannot open " << infoFile << std::endl; + return 1; + } + } + + if (verbose) std::cout << "load " << infoFile << std::endl; + + + RouterInfo ri(infoFile); + auto ident = ri.GetIdentHash(); + + + if (verbose) std::cout << "add " << ident.ToBase64() << " to " << fam << std::endl; + std::string sig; + if(CreateFamilySignature(fam, ident, privkey, sig)) { + ri.SetProperty(ROUTER_INFO_PROPERTY_FAMILY, fam); + ri.SetProperty(ROUTER_INFO_PROPERTY_FAMILY_SIG, sig); + if (verbose) std::cout << "signed " << sig << std::endl; + std::ofstream f(infoFile); + ri.WriteToStream(f); + } else { + std::cout << "failed to sign router info" << std::endl; + } + } + + if(verify) { + if(!infoFile.size()) { + std::cout << "no router info file specified" << std::endl; + return 1; + } + if(!certfile.size()) { + std::cout << "no family cerifiticate specified" << std::endl; + return 1; + } + auto v = LoadCertificate(certfile); + if(!v) { + std::cout << "invalid certificate" << std::endl; + return 1; + } + + { + std::ifstream i; + i.open(infoFile); + if(!i.is_open()) { + std::cout << "cannot open " << infoFile << std::endl; + return 1; + } + } + + if (verbose) std::cout << "load " << infoFile << std::endl; + + RouterInfo ri(infoFile); + auto sig = ri.GetProperty(ROUTER_INFO_PROPERTY_FAMILY_SIG); + if (ri.GetProperty(ROUTER_INFO_PROPERTY_FAMILY) != fam) { + std::cout << infoFile << " does not belong to " << fam << std::endl; + return 1; + } + auto ident = ri.GetIdentHash(); + + uint8_t buf[50]; + size_t len = fam.length(); + memcpy(buf, fam.c_str(), len); + memcpy(buf + len, (const uint8_t *) ident, 32); + len += 32; + uint8_t sigbuf[64]; + Base64ToByteStream(sig.c_str(), sig.length(), sigbuf, 64); + if(!v->Verify(buf, len, sigbuf)) { + std::cout << "invalid signature" << std::endl; + return 1; + } + } + return 0; +}