mirror of
https://github.com/PurpleI2P/i2pd-tools.git
synced 2025-01-22 13:27:17 +01:00
add famtool
This commit is contained in:
parent
189fabc2cc
commit
0ea81aed2e
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -8,6 +8,11 @@
|
||||||
# built binaries
|
# built binaries
|
||||||
keygen
|
keygen
|
||||||
keyinfo
|
keyinfo
|
||||||
|
famtool
|
||||||
|
|
||||||
# private key files
|
# private key files
|
||||||
*.dat
|
*.dat
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# certificates
|
||||||
|
*.crt
|
5
Makefile
5
Makefile
|
@ -11,7 +11,7 @@ OBJECTS = $(SOURCES:.cpp=.o)
|
||||||
I2PD_LIB = libi2pd.a
|
I2PD_LIB = libi2pd.a
|
||||||
|
|
||||||
|
|
||||||
all: keygen keyinfo
|
all: keygen keyinfo famtool
|
||||||
|
|
||||||
keygen: $(OBJECTS)
|
keygen: $(OBJECTS)
|
||||||
$(CXX) -o keygen keygen.o $(LDFLAGS) $(LIBS)
|
$(CXX) -o keygen keygen.o $(LDFLAGS) $(LIBS)
|
||||||
|
@ -19,6 +19,9 @@ keygen: $(OBJECTS)
|
||||||
keyinfo: $(OBJECTS)
|
keyinfo: $(OBJECTS)
|
||||||
$(CXX) -o keyinfo keyinfo.o $(LDFLAGS) $(LIBS)
|
$(CXX) -o keyinfo keyinfo.o $(LDFLAGS) $(LIBS)
|
||||||
|
|
||||||
|
famtool: $(OBJECTS)
|
||||||
|
$(CXX) -o famtool famtool.o $(LDFLAGS) $(LIBS)
|
||||||
|
|
||||||
$(OBJECTS): libi2pd.a
|
$(OBJECTS): libi2pd.a
|
||||||
|
|
||||||
.SUFFIXES:
|
.SUFFIXES:
|
||||||
|
|
374
famtool.cpp
Normal file
374
famtool.cpp
Normal file
|
@ -0,0 +1,374 @@
|
||||||
|
/**
|
||||||
|
* famtool - a tool for creating and verifying router families
|
||||||
|
*/
|
||||||
|
#include <cassert>
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include "RouterInfo.h"
|
||||||
|
#include "Base.h"
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/ec.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
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<Verifier> LoadCertificate (const std::string& filename)
|
||||||
|
{
|
||||||
|
std::shared_ptr<Verifier> 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<ECDSAP256Verifier>(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;
|
||||||
|
}
|
Loading…
Reference in a new issue