From: Guus Sliepen Date: Thu, 22 Jan 2009 14:44:12 +0000 (+0100) Subject: Update to a somewhat usable version. X-Git-Url: https://git.tinc-vpn.org/git/browse?a=commitdiff_plain;h=4887fb5f75d884730370a1963ee3a6f18dc39b94;p=fides Update to a somewhat usable version. Details about this version: - It is written in C++. - There is a fides class which implements all functionality. - It compiles to a command line version that allows you to test functionality. - It automatically creates a database in $HOME/.fides/. - It deals with trust and authorisation certificates. - It has a very simple web-of-trust implementation. --- diff --git a/Makefile b/Makefile index a712e4e..a7a4bd6 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,13 @@ +all: fides + +CFLAGS ?= -Wall -g -O0 +LDFLAGS ?= -Wall -g -O0 + fides: fides.o - $(CC) -o $@ $< + $(CXX) $(LDFLAGS) -o $@ $< -lbotan + +%.o: %.cc %.h + $(CXX) $(CFLAGS) -g -c -Wall -o $@ $< -%.o: %.c - $(CC) -c -o $@ $< +clean: + rm -f *.o fides diff --git a/fides.c b/fides.c deleted file mode 100644 index 70c8d3e..0000000 --- a/fides.c +++ /dev/null @@ -1,127 +0,0 @@ -#include -#include - -void help(FILE *out, const char *argv0) { - fprintf(out, "Usage: %s [arguments]\n\n", argv0); - fprintf(out, "Available commands are:\n"); - fprintf(out, " init Initialise fides, generate a public/private keypair.\n"); - fprintf(out, " version Show version and copyright information.\n"); - fprintf(out, " help Show this help message.\n"); - fprintf(out, " trust \n"); - fprintf(out, " Trust allow/deny packets signed by the specified key.\n"); - fprintf(out, " distrust \n"); - fprintf(out, " Distrust allow/deny packets signed by the specified key.\n"); - fprintf(out, " allow \n"); - fprintf(out, " Allow the entity identified by keyid some stuff.\n"); - fprintf(out, " deny \n"); - fprintf(out, " Deny the entity identified by keyid some stuff.\n"); - fprintf(out, " import [filename]\n"); - fprintf(out, " Import trust packets from file, or stdin if unspecified.\n"); - fprintf(out, " export [filename]\n"); - fprintf(out, " Export trust packets to file, or stdout if unspecified.\n"); - fprintf(out, " test \n"); - fprintf(out, " Tell whether stuff is allowed or not, and why.\n"); - fprintf(out, " fsck Verify the signature on all information collected.\n"); -} - -int version() { - fprintf(stdout, "fides version 0.1\n"); - fprintf(stdout, "Copyright (c) 2008 Guus Sliepen \n\n"); - fprintf(stdout, "This program is free software; you can redistribute it and/or modify\n" - "it under the terms of the GNU General Public License as published by\n" - "the Free Software Foundation; either version 2 of the License, or\n" - "(at your option) any later version.\n"); - - return 0; -} - -int init() { - // Generate a public/private keypair if it does not already exist - return 0; -} - -int trust(int argc, char *argv[]) { - // Trust another key - return 0; -} - -int distrust(int argc, char *argv[]) { - // Distrust another key - return 0; -} - -int allow(int argc, char *argv[]) { - // Generate a certficate allowing something - return 0; -} - -int deny(int argc, char *argv[]) { - // Generate a certificate denying something - return 0; -} - -int import(int argc, char *argv[]) { - // Import certificates - return 0; -} - -int export(int argc, char *argv[]) { - // Export certificates - return 0; -} - -int test(int argc, char *argv[]) { - // Test something against all certificates - return 0; -} - -int fsck() { - // Verify the integrity of all certificates - return 0; -} - -main(int argc, char *argv[]) { - if(argc < 2) { - help(stderr, argv[0]); - return EX_USAGE; - } - - if(!strcmp(argv[1], "help")) { - help(stdout, argv[0]); - return 0; - } - - if(!strcmp(argv[1], "version")) - return version(); - - if(!strcmp(argv[1], "init")) - return init(); - - if(!strcmp(argv[1], "trust")) - return trust(argc - 2, argv + 2); - - if(!strcmp(argv[1], "distrust")) - return distrust(argc - 2, argv + 2); - - if(!strcmp(argv[1], "allow")) - return allow(argc - 2, argv + 2); - - if(!strcmp(argv[1], "deny")) - return deny(argc - 2, argv + 2); - - if(!strcmp(argv[1], "import")) - return import(argc - 2, argv + 2); - - if(!strcmp(argv[1], "export")) - return export(argc - 2, argv + 2); - - if(!strcmp(argv[1], "test")) - return test(argc - 2, argv + 2); - - if(!strcmp(argv[1], "fsck")) - return fsck(); - - fprintf(stderr, "Unknown command '%s'\n", argv[1]); - return EX_USAGE; -} - diff --git a/fides.cc b/fides.cc new file mode 100644 index 0000000..eb2de1a --- /dev/null +++ b/fides.cc @@ -0,0 +1,1118 @@ +/* fides.c - Light-weight, decentralised trust and authorisation management + Copyright (C) 2008-2009 Guus Sliepen + + Fides is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + Fides is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program; if not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fides.h" + +using namespace std; + +// Global state + +Botan::LibraryInitializer libinit; +Botan::AutoSeeded_RNG fides::rng; + +// Public key functions + +fides::publickey::publickey(): pub(0), trust(0) { +} + +fides::publickey::~publickey() { + delete pub; +} + +void fides::publickey::load(istream &in) { + try { + Botan::DataSource_Stream source(in); + pub = dynamic_cast(Botan::X509::load_key(source)); + } catch(Botan::Exception &e) { + throw exception(e.what()); + } +} + +void fides::publickey::load(const std::string &filename) { + ifstream in(filename.c_str()); + load(in); +} + +void fides::publickey::save(ostream &out) { + out << to_string(); +} + +void fides::publickey::save(const std::string &filename) { + ofstream out(filename.c_str()); + save(out); +} + +void fides::publickey::from_string(const std::string &in) { + try { + Botan::DataSource_Memory source(in); + pub = dynamic_cast(Botan::X509::load_key(source)); + } catch(Botan::Exception &e) { + throw exception(e.what()); + } +} + +string fides::publickey::to_string() { + return Botan::X509::PEM_encode(*pub); +} + +string fides::publickey::fingerprint(unsigned int bits) { + // TODO: find out if there is a standard way to get a hash of an ECDSA public key + Botan::SHA_256 sha256; + Botan::SecureVector hash = sha256.process(Botan::X509::PEM_encode(*pub)); + return string((const char *)hash.begin(), bits / 8); +} + +bool fides::publickey::verify(const std::string &statement, const std::string &signature) { + auto_ptr verifier(Botan::get_pk_verifier(*pub, "EMSA1(SHA-512)")); + verifier->update((const Botan::byte *)statement.data(), statement.size()); + Botan::SecureVector sig; + sig.set((const Botan::byte *)signature.data(), signature.size()); + return verifier->check_signature(sig); +} + +// Private key functions + +fides::privatekey::privatekey(): priv(0) { +} + +fides::privatekey::~privatekey() { + delete priv; + pub = 0; +} + +void fides::privatekey::generate(const std::string &field) { + Botan::EC_Domain_Params domain = Botan::get_EC_Dom_Pars_by_oid(field); + pub = priv = new Botan::ECDSA_PrivateKey(rng, domain); +} + +void fides::privatekey::generate(unsigned int bits) { + switch(bits) { + case 112: return generate("1.3.132.0.6"); + case 128: return generate("1.3.132.0.28"); + case 160: return generate("1.3.132.0.9"); + case 192: return generate("1.3.132.0.31"); + case 224: return generate("1.3.132.0.32"); + case 256: return generate("1.3.132.0.10"); + case 384: return generate("1.3.132.0.34"); + case 521: return generate("1.3.132.0.35"); + default: throw exception("Unsupported number of bits for private key"); + } +} + +void fides::privatekey::load_private(istream &in) { + try { + Botan::DataSource_Stream stream(in); + pub = priv = dynamic_cast(Botan::PKCS8::load_key(stream, rng, "")); + } catch(Botan::Exception &e) { + throw exception(e.what()); + } +} + +void fides::privatekey::load_private(const std::string &filename) { + ifstream in(filename.c_str()); + load_private(in); +} + +void fides::privatekey::save_private(ostream &out) { + out << Botan::PKCS8::PEM_encode(*priv); +} + +void fides::privatekey::save_private(const std::string &filename) { + ofstream out(filename.c_str()); + save_private(out); +} + +string fides::privatekey::sign(const std::string &statement) { + auto_ptr signer(Botan::get_pk_signer(*priv, "EMSA1(SHA-512)")); + Botan::SecureVector sig = signer->sign_message((const Botan::byte *)statement.data(), statement.size(), rng); + return string((const char *)sig.begin(), (size_t)sig.size()); +} + +// Base64 and hex encoding/decoding functions + +string fides::hexencode(const string &in) { + Botan::Pipe pipe(new Botan::Hex_Encoder); + pipe.process_msg((Botan::byte *)in.data(), in.size()); + return pipe.read_all_as_string(); +} + +string fides::hexdecode(const string &in) { + Botan::Pipe pipe(new Botan::Hex_Decoder); + pipe.process_msg((Botan::byte *)in.data(), in.size()); + return pipe.read_all_as_string(); +} + +string fides::b64encode(const string &in) { + Botan::Pipe pipe(new Botan::Base64_Encoder); + pipe.process_msg((Botan::byte *)in.data(), in.size()); + return pipe.read_all_as_string(); +} + +string fides::b64decode(const string &in) { + Botan::Pipe pipe(new Botan::Base64_Decoder); + pipe.process_msg((Botan::byte *)in.data(), in.size()); + return pipe.read_all_as_string(); +} + +// Certificate functions + +fides::certificate::certificate(publickey *key, struct timeval timestamp, const std::string &statement, const std::string &signature): signer(key), timestamp(timestamp), statement(statement), signature(signature) {} + +bool fides::certificate::validate() { + string data = signer->fingerprint(256); + data += string((const char *)×tamp, sizeof timestamp); + data += statement; + return signer->verify(data, signature); +} + +fides::certificate::certificate(privatekey *key, struct timeval timestamp, const std::string &statement): signer(key), timestamp(timestamp), statement(statement) { + string data = signer->fingerprint(256); + data += string((const char *)×tamp, sizeof timestamp); + data += statement; + signature = key->sign(data); +} + +string fides::certificate::fingerprint(unsigned int bits) { + return signature.substr(signature.size() - bits / 8); +} + +string fides::certificate::to_string() const { + string data = fides::hexencode(signer->fingerprint()); + data += ' '; + char ts[100]; + snprintf(ts, sizeof ts, "%lu.%06lu", timestamp.tv_sec, timestamp.tv_usec); + data += ts; + data += ' '; + data += fides::b64encode(signature); + data += ' '; + data += statement; + return data; +} + +static void help(ostream &out, const string &argv0) { + out << "Usage: " << argv0 << " [arguments]\n" + "\n" + "Available commands are:\n" + "\n" + " init Initialise fides, generate a public/private keypair.\n" + " version Show version and copyright information.\n" + " help Show this help message.\n" + "\n" + " trust \n" + " Trust allow/deny packets signed by the specified key.\n" + " distrust \n" + " Distrust allow/deny packets signed by the specified key.\n" + " dctrust \n" + " Don't care about allow/deny packets signed by the specified key.\n" + " is_trusted \n" + " Returns 0 if key is trusted, 1 otherwise\n" + " is_distrusted \n" + " Returns 0 if key is distrusted, 1 otherwise\n" + "\n" + " sign \n" + " Sign stuff.\n" + " allow \n" + " Allow stuff.\n" + " deny \n" + " Deny stuff.\n" + " dontcare \n" + " Don't care about stuff.\n" + " is_allowed \n" + " Returns 0 if stuff is allowed, 1 otherwise\n" + " is_denied \n" + " Returns 0 if stuff is denied, 1 otherwise\n" + "\n" + " import [filename]\n" + " Import keys and certificates from file, or stdin if unspecified.\n" + " export [filename]\n" + " Export keys and certificates to file, or stdout if unspecified.\n" + " test \n" + " Tell whether stuff is allowed or not by counting relevant certificates\n" + " find \n" + " Find all certificates matching regexp\n" + " fsck Verify the signature on all information collected.\n"; +} + +static void version(ostream &out = cout) { + out << "fides version 0.1\n" + "Copyright (c) 2008-2009 Guus Sliepen \n" + "\n" + "This program is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation; either version 2 of the License, or\n" + "(at your option) any later version.\n"; +} + +// Utility functions + +static vector dirlist(const string &path) { + vector files; + + DIR *dir = opendir(path.c_str()); + if(!dir) + return files; + + struct dirent entry, *result = &entry; + + while(result) { + readdir_r(dir, &entry, &result); + if(!result) + break; + struct stat st; + if(result->d_type == DT_UNKNOWN) { + if(stat((path + "/" + result->d_name).c_str(), &st)) + continue; + if(S_ISREG(st.st_mode)) + files.push_back(result->d_name); + } else if(result->d_type == DT_REG) { + files.push_back(result->d_name); + } + } + + closedir(dir); + + return files; +} + +void fides::certificate_save(const certificate *cert, const string &filename) { + ofstream file(filename.c_str()); + file << cert->to_string() << '\n'; +} + +fides::certificate *fides::certificate_load(const string &filename) { + ifstream file(filename.c_str()); + string data; + getline(file, data); + return certificate_from_string(data); +} + +fides::certificate *fides::certificate_from_string(const string &data) { + size_t b, e; + e = data.find(' ', 0); + if(e == string::npos) + throw exception("Invalid certificate"); + string fingerprint = hexdecode(data.substr(0, e)); + publickey *signer = find_key(fingerprint); + if(!signer) + throw exception("Unknown public key"); + b = e + 1; + e = data.find('.', b); + if(e == string::npos) + throw exception("Invalid certificate"); + struct timeval timestamp; + timestamp.tv_sec = atol(data.c_str() + b); + b = e + 1; + timestamp.tv_usec = atol(data.c_str() + b); + e = data.find(' ', b); + if(e == string::npos) + throw exception("Invalid certificate"); + b = e + 1; + e = data.find(' ', b); + if(e == string::npos) + throw exception("Invalid certificate"); + string signature = fides::b64decode(data.substr(b, e - b)); + b = e + 1; + string statement = data.substr(b); + + return new certificate(signer, timestamp, statement, signature); +} + +// Fides main functions + +fides::fides(const string &dir): homedir(dir) { + cerr << "Fides initialising\n"; + + // Set homedir to provided directory, or $FIDES_HOME, or $HOME/.fides, or as a last resort $PWD/.fides + if(homedir.empty()) + homedir = getenv("FIDES_HOME") ?: ""; + if(homedir.empty()) { + char cwd[PATH_MAX]; + homedir = getenv("HOME") ?: getcwd(cwd, sizeof cwd); + homedir += "/.fides"; + } + + // Derived directories + homedir += '/'; + certdir = homedir + "certs/"; + keydir = homedir + "keys/"; + obsoletedir = homedir + ".obsolete_certs/"; + + // Ensure the homedir and its subdirectories exist + mkdir(homedir.c_str(), 0700); + mkdir(certdir.c_str(), 0700); + mkdir(keydir.c_str(), 0700); + mkdir(obsoletedir.c_str(), 0700); + + try { + mykey.load_private(homedir + "priv"); + firstrun = false; + } catch(fides::exception &e) { + cerr << "Fides generating keypair\n"; + mykey.generate(); + mykey.save_private(homedir + "priv"); + mykey.save(keydir + hexencode(mykey.fingerprint())); + firstrun = true; + } + vector files = dirlist(keydir); + for(size_t i = 0; i < files.size(); ++i) { + cerr << "Loading key " << files[i] << '\n'; + + publickey *key = new publickey(); + key->load(keydir + files[i]); + keys[hexdecode(files[i])] = key; + } + + keys[mykey.fingerprint()] = &mykey; + + files = dirlist(certdir); + for(size_t i = 0; i < files.size(); ++i) { + cerr << "Loading certificate " << files[i] << '\n'; + certificate *cert = certificate_load(certdir + files[i]); + if(false && !cert->validate()) { + cerr << "Bad certificate!\n"; + continue; + } + certs[hexdecode(files[i])] = cert; + } + + // TODO: save and load this value + latest.tv_sec = 0; + latest.tv_usec = 0; + + update_trust(); +} + +fides::~fides() { + cerr << "Fides exitting\n"; + for(map::iterator i = certs.begin(); i != certs.end(); ++i) + delete i->second; + for(map::iterator i = keys.begin(); i != keys.end(); ++i) + if(i->second != &mykey) + delete i->second; +} + +bool fides::fsck() { + int errors = 0; + + for(map::iterator i = certs.begin(); i != certs.end(); ++i) { + if(!i->second->validate()) { + cerr << "Validation of certificate failed: " << i->second->to_string() << '\n'; + errors++; + } + } + + cerr << errors << " errors in " << certs.size() << " certificates\n"; + return !errors; +} + +string fides::get_homedir() { + return homedir; +} + +bool fides::is_firstrun() { + return firstrun; +} + +fides::publickey *fides::find_key(const string &fingerprint) { + map::iterator i; + i = keys.find(fingerprint); + if(i != keys.end()) + return i->second; + else + return 0; +} + +vector fides::find_certificates(publickey *signer, const string ®ex) { + vector found; + map::iterator i; + regexp regexp(regex); + for(i = certs.begin(); i != certs.end(); ++i) { + if(!i->second) { + cerr << "No certificate for " << hexencode(i->first) << '\n'; + continue; + } + if(i->second->signer == signer) + if(regexp.match(i->second->statement)) + found.push_back(i->second); + } + return found; +} + +vector fides::find_certificates(const string ®ex) { + vector found; + map::iterator i; + regexp regexp(regex); + for(i = certs.begin(); i != certs.end(); ++i) + if(regexp.match(i->second->statement)) + found.push_back(i->second); + return found; +} + +vector fides::find_certificates(publickey *signer) { + vector found; + map::iterator i; + for(i = certs.begin(); i != certs.end(); ++i) + if(i->second->signer == signer) + found.push_back(i->second); + return found; +} + +void fides::import_all(istream &in) { + string line, pem; + bool is_pem = false; + + while(getline(in, line)) { + if(line.empty()) + continue; + + if(is_pem || !line.compare(0, 11, "-----BEGIN ")) { + pem += line + '\n'; + if(!line.compare(0, 9, "-----END ")) { + fides::publickey *key = new publickey(); + key->from_string(pem); + cerr << "Imported key " << hexencode(key->fingerprint()) << '\n'; + merge(key); + is_pem = false; + } else { + is_pem = true; + } + continue; + } + + fides::certificate *cert = certificate_from_string(line); + cerr << "Importing certificate " << hexencode(cert->fingerprint()) << '\n'; + merge(cert); + } +} + +void fides::export_all(ostream &out) { + for(map::iterator i = keys.begin(); i != keys.end(); ++i) + out << i->second->to_string(); + for(map::iterator i = certs.begin(); i != certs.end(); ++i) + out << i->second->to_string() << '\n'; +} + +static int init() { + fides fides; + if(fides.is_firstrun()) { + cout << "New keys generated in " << fides.get_homedir() << '\n'; + } else { + cout << "Fides already initialised\n"; + } + return 0; +} + +void fides::trust(publickey *key) { + string full = "t+ " + hexencode(key->fingerprint()); + sign(full); +} + +void fides::distrust(publickey *key) { + string full = "t- " + hexencode(key->fingerprint()); + sign(full); +} + +void fides::dctrust(publickey *key) { + string full = "t0 " + hexencode(key->fingerprint()); + sign(full); +} + +static int is_trusted(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + fides::publickey *key = fides.find_key(fides::hexdecode(argv[0])); + if(!key) { + cerr << "Unknown key!\n"; + return 1; + } + return fides.is_trusted(key) ? 0 : 1; +} + +static int is_distrusted(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + fides::publickey *key = fides.find_key(fides::hexdecode(argv[0])); + if(!key) { + cerr << "Unknown key!\n"; + return 1; + } + return fides.is_distrusted(key) ? 0 : 1; +} + +static int trust(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + fides::publickey *key = fides.find_key(fides::hexdecode(argv[0])); + if(key) + fides.trust(key); + else { + cerr << "Unknown key!\n"; + return -1; + } + return 0; +} + +static int dctrust(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + fides::publickey *key = fides.find_key(fides::hexdecode(argv[0])); + if(key) + fides.dctrust(key); + else { + cerr << "Unknown key!\n"; + return -1; + } + return 0; +} + +static int distrust(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + fides::publickey *key = fides.find_key(fides::hexdecode(argv[0])); + if(key) + fides.distrust(key); + else { + cerr << "Unknown key!\n"; + return -1; + } + return 0; +} + +void fides::update_trust() { + // clear trust on all keys + for(map::iterator i = keys.begin(); i != keys.end(); ++i) + i->second->trust = 0; + + // Start by checking all trust certificates from ourself. + // If another key is positively or negatively trusted, update its trust score + // and add it to the the list of new keys to check. + // Then add our own key to the list of already checked keys. + // Then check all the trust certificates of those on the tocheck list, etc. + // Already checked keys are never updated anymore (TODO: is that smart?) + // Certificates of keys with a zero or negative trust score are not processed. + + set checked; + set tocheck; + set newkeys; + set::iterator i; + + mykey.trust = 3; + tocheck.insert(&mykey); + + while(tocheck.size()) { + // add + checked.insert(tocheck.begin(), tocheck.end()); + newkeys.clear(); + + // loop over all keys whose certificates need to be checked + + for(i = tocheck.begin(); i != tocheck.end(); ++i) { + cerr << "Trust for key " << hexencode((*i)->fingerprint()) << " set to " << (*i)->trust << '\n'; + + // except if this key is not trusted + + if((*i)->trust <= 0) + continue; + + // find all non-zero trust certificates of this key + + vector matches = find_certificates(*i, "^t[+-] "); + + // update trust value of those keys + + for(size_t j = 0; j < matches.size(); j++) { + publickey *other = find_key(hexdecode(matches[j]->statement.substr(3))); + + if(!other) { + cerr << "Trust certificate for unknown key: " << matches[j]->to_string() << '\n'; + continue; + } + + // except for keys we already checked + + if(checked.find(other) != checked.end()) { + cerr << "Skipping trust certificate for already checked key: " << matches[j]->to_string() << '\n'; + continue; + } + + // update trust + + if(matches[j]->statement[1] == '+') + other->trust++; + else + other->trust--; + + newkeys.insert(other); + } + } + + tocheck = newkeys; + } +} + +void fides::merge(publickey *key) { + if(keys.find(key->fingerprint()) != keys.end()) { + cerr << "Key already known\n"; + return; + } + + keys[key->fingerprint()] = key; + key->save(keydir + hexencode(key->fingerprint())); +} + +void fides::merge(certificate *cert) { + // TODO: check if cert is already in database + // TODO: check if cert obsoletes other certs + + // If we already know this certificate, drop it. + if(certs.find(cert->fingerprint()) != certs.end()) { + cerr << "Certificate already known\n"; + return; + } + + // If the certificate does not validate, drop it. + if(!cert->validate()) { + // TODO: this should not happen, be wary of DoS attacks + cerr << "Certificate invalid\n"; + return; + } + + // TODO: move these regexps to the class? + regexp authexp("^a[+0-] "); + regexp trustexp("^t[+0-] "); + vector others; + + // Is this an authorisation cert? + if(authexp.match(cert->statement)) { + // Find certs identical except for the +/-/0 + // TODO: escape statement in regexp + others = find_certificates(cert->signer, string("^a[+0-] ") + cert->statement.substr(3) + '$'); + if(others.size()) { + if(timercmp(&others[0]->timestamp, &cert->timestamp, >)) { + cerr << "Certificate is overruled by a newer certificate\n"; + return; + } + if(timercmp(&others[0]->timestamp, &cert->timestamp, ==)) { + // TODO: this should not happen, be wary of DoS attacks + cerr << "Certificate has same timestamp as another timestamp!\n"; + return; + } + cerr << "Certificate overrules an older certificate!\n"; + // save new cert first + certificate_save(cert, certdir + hexencode(cert->fingerprint())); + certs[cert->fingerprint()] = cert; + + // delete old one + rename((certdir + hexencode(others[0]->fingerprint())).c_str(), (obsoletedir + hexencode(others[0]->fingerprint())).c_str()); + certs.erase(others[0]->fingerprint()); + delete others[0]; + return; + } + } + + // Is this a trust cert? + // TODO: it's just the same as above! + if(trustexp.match(cert->statement)) { + // Find certs identical except for the +/-/0 + // TODO: escape statement in regexp + others = find_certificates(cert->signer, string("^t[+0-] ") + cert->statement.substr(3) + '$'); + if(others.size()) { + if(timercmp(&others[0]->timestamp, &cert->timestamp, >)) { + cerr << "Certificate is overruled by a newer certificate\n"; + return; + } + if(timercmp(&others[0]->timestamp, &cert->timestamp, ==)) { + // TODO: this should not happen, be wary of DoS attacks + cerr << "Certificate has same timestamp as another timestamp!\n"; + return; + } + cerr << "Certificate overrules an older certificate!\n"; + // delete old one + rename((certdir + hexencode(others[0]->fingerprint())).c_str(), (obsoletedir + hexencode(others[0]->fingerprint())).c_str()); + certs.erase(others[0]->fingerprint()); + delete others[0]; + certs[cert->fingerprint()] = cert; + certificate_save(cert, certdir + hexencode(cert->fingerprint())); + return; + } + } + + // Did somebody sign the exact same statement twice? + // Could happen if there is a different, conflicting statement between this new and the corresponding old one. + others = find_certificates(cert->signer, string("^") + cert->statement + '$'); + if(others.size()) { + if(timercmp(&others[0]->timestamp, &cert->timestamp, >)) { + cerr << "Certificate is overruled by a newer certificate\n"; + return; + } + if(timercmp(&others[0]->timestamp, &cert->timestamp, ==)) { + // TODO: this should not happen, be wary of DoS attacks + cerr << "Certificate has same timestamp as another timestamp!\n"; + return; + } + cerr << "Certificate overrules an older certificate!\n"; + // delete old one + rename((certdir + hexencode(others[0]->fingerprint())).c_str(), (obsoletedir + hexencode(others[0]->fingerprint())).c_str()); + certs.erase(others[0]->fingerprint()); + delete others[0]; + certs[cert->fingerprint()] = cert; + certificate_save(cert, certdir + hexencode(cert->fingerprint())); + return; + } + + cerr << "Certificate is new\n"; + certs[cert->fingerprint()] = cert; + certificate_save(cert, certdir + hexencode(cert->fingerprint())); +} + +void fides::auth_stats(const string &statement, int &self, int &trusted, int &all) { + self = trusted = all = 0; + vector matches = find_certificates(string("^a[+0-] ") + statement + '$'); + for(size_t i = 0; i < matches.size(); ++i) { + char code = matches[i]->statement[1]; + int diff = 0; + if(code == '+') + diff = 1; + else if(code == '-') + diff = -1; + if(matches[i]->signer == &mykey) + self += diff; + if(matches[i]->signer->trust > 0) + trusted += diff; + all += diff; + } +} + +bool fides::is_trusted(publickey *key) { + return key->trust > 0; +} + +bool fides::is_distrusted(publickey *key) { + return key->trust < 0; +} + +bool fides::is_allowed(const string &statement, publickey *key) { + int self, trusted, all; + + if(key) + auth_stats(hexencode(key->fingerprint()) + " " + statement, self, trusted, all); + else + auth_stats(statement, self, trusted, all); + + if(self) + return self > 0; + else if(trusted) + return trusted > 0; + else + return false; +} + +bool fides::is_denied(const string &statement, publickey *key) { + int self, trusted, all; + + if(key) + auth_stats(hexencode(key->fingerprint()) + " " + statement, self, trusted, all); + else + auth_stats(statement, self, trusted, all); + + if(self) + return self < 0; + else if(trusted) + return trusted < 0; + else + return false; +} + +void fides::sign(const string &statement) { + // Try to set "latest" to now, but ensure monoticity + struct timeval now; + gettimeofday(&now, 0); + if(timercmp(&latest, &now, >=)) { + latest.tv_usec++; + if(latest.tv_usec >= 1000000) { + latest.tv_sec++; + latest.tv_usec -= 1000000; + } + } else { + latest = now; + } + + // Create a new certificate and merge it with our database + merge(new certificate(&mykey, latest, statement)); +} + +void fides::allow(const string &statement, publickey *key) { + string full = "a+ "; + if(key) + full += hexencode(key->fingerprint()) + ' '; + full += statement; + sign(full); +} + +void fides::dontcare(const string &statement, publickey *key) { + string full = "a0 "; + if(key) + full += hexencode(key->fingerprint()) + ' '; + full += statement; + sign(full); +} + +void fides::deny(const string &statement, publickey *key) { + string full = "a- "; + if(key) + full += hexencode(key->fingerprint()) + ' '; + full += statement; + sign(full); +} + +static int sign(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + fides.sign(argv[0]); + return 0; +} + +static int allow(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + fides.allow(argv[0]); + return 0; +} + +static int dontcare(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + fides.dontcare(argv[0]); + return 0; +} + +static int deny(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + fides.deny(argv[0]); + return 0; +} + +static int import(int argc, char *const argv[]) { + fides fides; + + if(argc) { + ifstream in(argv[0]); + fides.import_all(in); + } else + fides.import_all(cin); + return 0; +} + +static int exprt(int argc, char *const argv[]) { + fides fides; + + if(argc) { + ofstream out(argv[0]); + fides.export_all(out); + } else + fides.export_all(cout); + return 0; +} + +static int find(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + // Find certificates matching statement + fides fides; + const vector &certs = fides.find_certificates(argv[0]); + for(size_t i = 0; i < certs.size(); ++i) + cout << i << ' ' << certs[i]->to_string() << '\n'; + return 0; +} + +static int is_allowed(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + return fides.is_allowed(argv[0]) ? 0 : 1; +} + +static int is_denied(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + return fides.is_denied(argv[0]) ? 0 : 1; +} + +static int test(int argc, char *const argv[]) { + if(argc < 1) + return EX_USAGE; + + fides fides; + int self, trusted, all; + fides.auth_stats(argv[0], self, trusted, all); + cout << "Self: " << self << ", trusted: " << trusted << ", all: " << all << '\n'; + return 0; +} + +static int fsck() { + fides fides; + if(fides.fsck()) { + cout << "Everything OK\n"; + return 0; + } else { + cout << "Integrity failure!\n"; + return 1; + } +} + +int main(int argc, char *const argv[]) { + int r; + int option_index; + + static struct option const long_options[] = { + {"homedir", required_argument, NULL, 2}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 3}, + {NULL, 0, NULL, 0} + }; + + while((r = getopt_long(argc, argv, "h", long_options, &option_index)) != EOF) { + switch (r) { + case 0: /* long option */ + break; + case 1: /* non-option */ + break; + case 2: + //homedir = strdup(optarg); + break; + case 3: + version(); + return 0; + case 'h': + help(cout, argv[0]); + return 0; + } + } + + if(argc < 2) { + help(cerr, argv[0]); + return EX_USAGE; + } + + if(!strcmp(argv[1], "help")) { + help(cout, argv[0]); + return 0; + } + + if(!strcmp(argv[1], "version")) { + version(); + return 0; + } + + if(!strcmp(argv[1], "init")) + return init(); + + if(!strcmp(argv[1], "trust")) + return trust(argc - 2, argv + 2); + + if(!strcmp(argv[1], "dctrust")) + return dctrust(argc - 2, argv + 2); + + if(!strcmp(argv[1], "distrust")) + return distrust(argc - 2, argv + 2); + + if(!strcmp(argv[1], "is_trusted")) + return is_trusted(argc - 2, argv + 2); + + if(!strcmp(argv[1], "is_distrusted")) + return is_distrusted(argc - 2, argv + 2); + + if(!strcmp(argv[1], "is_allowed")) + return is_allowed(argc - 2, argv + 2); + + if(!strcmp(argv[1], "is_denied")) + return is_denied(argc - 2, argv + 2); + + if(!strcmp(argv[1], "allow")) + return allow(argc - 2, argv + 2); + + if(!strcmp(argv[1], "dontcare")) + return dontcare(argc - 2, argv + 2); + + if(!strcmp(argv[1], "deny")) + return deny(argc - 2, argv + 2); + + if(!strcmp(argv[1], "sign")) + return sign(argc - 2, argv + 2); + + if(!strcmp(argv[1], "import")) + return import(argc - 2, argv + 2); + + if(!strcmp(argv[1], "export")) + return exprt(argc - 2, argv + 2); + + if(!strcmp(argv[1], "test")) + return test(argc - 2, argv + 2); + + if(!strcmp(argv[1], "find")) + return find(argc - 2, argv + 2); + + if(!strcmp(argv[1], "fsck")) + return fsck(); + + cerr << "Unknown command: " << argv[1] << '\n'; + return EX_USAGE; +} + diff --git a/fides.h b/fides.h new file mode 100644 index 0000000..7d94719 --- /dev/null +++ b/fides.h @@ -0,0 +1,170 @@ +#ifndef __FIDES_H__ +#define __FIDES_H__ + +#include +#include +#include +#include +#include + +class fides { + std::string homedir; + std::string certdir; + std::string obsoletedir; + std::string keydir; + + bool firstrun; + struct timeval latest; + static Botan::AutoSeeded_RNG rng; + + public: + // Utility functions + + static std::string b64encode(const std::string &in); + static std::string b64decode(const std::string &in); + static std::string hexencode(const std::string &in); + static std::string hexdecode(const std::string &in); + + class regexp { + regex_t comp; + + public: + static const int EXTENDED = REG_EXTENDED; + static const int ICASE = REG_ICASE; + static const int NOSUB = REG_NOSUB; + static const int NEWLINE = REG_NEWLINE; + + static const int NOTBOL = REG_NOTBOL; + static const int NOTEAL = REG_NOTEOL; + + regexp(const std::string &exp, int cflags = 0) { + int err = regcomp(&comp, exp.c_str(), cflags | NOSUB); + if(err) + throw exception("Could not compile regular expression"); + } + + ~regexp() { + regfree(&comp); + } + + bool match(const std::string &in, int eflags = 0) { + return regexec(&comp, in.c_str(), 0, 0, eflags) == 0; + } + }; + + // Exception class + + class exception: public std::runtime_error { + public: + exception(const std::string reason): runtime_error(reason) {} + }; + + // Objects manipulated by fides + + class publickey { + protected: + Botan::ECDSA_PublicKey *pub; + + public: + publickey(); + ~publickey(); + + int trust; + void load(std::istream &in); + void save(std::ostream &out); + void load(const std::string &filename); + void save(const std::string &filename); + bool verify(const std::string &data, const std::string &signature); + std::string to_string(); + void from_string(const std::string &in); + std::string fingerprint(unsigned int bits = 64); + }; + + class privatekey: public publickey { + Botan::ECDSA_PrivateKey *priv; + + public: + privatekey(); + ~privatekey(); + + void load_private(std::istream &in); + void save_private(std::ostream &out); + void load_private(const std::string &filename); + void save_private(const std::string &filename); + void generate(const std::string &field); + void generate(unsigned int bits = 224); + std::string sign(const std::string &data); + }; + + class certificate { + friend class fides; + publickey *signer; + struct timeval timestamp; + std::string statement; + std::string signature; + + public: + certificate(publickey *pub, struct timeval timestamp, const std::string &statement, const std::string &signature); + certificate(privatekey *priv, struct timeval timestamp, const std::string &statement); + + std::string to_string() const; + std::string fingerprint(unsigned int bits = 64); + bool validate(); + }; + + // Fides class itself + + private: + privatekey mykey; + std::map keys; + std::map certs; + std::set trustedkeys; + + void merge(certificate *cert); + void merge(publickey *key); + + public: + fides(const std::string &homedir = ""); + ~fides(); + + bool is_firstrun(); + bool fsck(); + std::string get_homedir(); + + void sign(const std::string &statement); + + void allow(const std::string &statement, publickey *key = 0); + void dontcare(const std::string &statement, publickey *key = 0); + void deny(const std::string &statement, publickey *key = 0); + bool is_allowed(const std::string &statement, publickey *key = 0); + bool is_denied(const std::string &statement, publickey *key = 0); + + void auth_stats(const std::string &statement, int &self, int &trusted, int &all); + void trust(publickey *key); + void dctrust(publickey *key); + void distrust(publickey *key); + bool is_trusted(publickey *key); + bool is_distrusted(publickey *key); + publickey *find_key(const std::string &fingerprint); + void update_trust(); + + std::vector find_certificates(publickey *key, const std::string &statement); + std::vector find_certificates(const std::string &statement); + std::vector find_certificates(publickey *key); + + certificate *import_certificate(const std::string &certificate); + std::string export_certificate(const certificate *); + + publickey *import_key(const std::string &key); + std::string export_key(const publickey *key); + + void import_all(std::istream &in); + void export_all(std::ostream &out); + + certificate *certificate_from_string(const std::string &certificate); + certificate *certificate_load(const std::string &filename); + void certificate_save(const certificate *cert, const std::string &filename); + +}; + +#endif