1 /* fides.cc - Light-weight, decentralised trust and authorisation management
2 Copyright (C) 2008-2009 Guus Sliepen <guus@tinc-vpn.org>
4 Fides is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of
7 the License, or (at your option) any later version.
9 Fides is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public
15 License along with this program; if not, see <http://www.gnu.org/licenses/>.
21 #include <sys/types.h>
25 #include <botan/types.h>
26 #include <botan/botan.h>
27 #include <botan/ecdsa.h>
28 #include <botan/look_pk.h>
29 #include <botan/lookup.h>
30 #include <botan/filters.h>
31 #include <botan/sha2_32.h>
37 #define FIDES_DEBUG false
40 #define debug if(FIDES_DEBUG)
46 Botan::AutoSeeded_RNG fides::rng;
48 // Public key functions
50 fides::publickey::publickey(): pub(0), trust(0) {
53 fides::publickey::~publickey() {
57 void fides::publickey::load(istream &in) {
59 Botan::DataSource_Stream source(in);
60 pub = dynamic_cast<Botan::ECDSA_PublicKey *>(Botan::X509::load_key(source));
61 } catch(Botan::Exception &e) {
62 throw exception(e.what());
66 void fides::publickey::load(const std::string &filename) {
67 ifstream in(filename.c_str());
71 void fides::publickey::save(ostream &out) const {
75 void fides::publickey::save(const std::string &filename) const {
76 ofstream out(filename.c_str());
80 void fides::publickey::from_string(const std::string &in) {
82 Botan::DataSource_Memory source(in);
83 pub = dynamic_cast<Botan::ECDSA_PublicKey *>(Botan::X509::load_key(source));
84 } catch(Botan::Exception &e) {
85 throw exception(e.what());
89 string fides::publickey::to_string() const {
90 return Botan::X509::PEM_encode(*pub);
93 string fides::publickey::fingerprint(unsigned int bits) const {
94 // TODO: find out if there is a standard way to get a hash of an ECDSA public key
95 Botan::SHA_256 sha256;
96 Botan::SecureVector<Botan::byte> hash = sha256.process(Botan::X509::PEM_encode(*pub));
97 return string((const char *)hash.begin(), bits / 8);
100 bool fides::publickey::verify(const std::string &statement, const std::string &signature) const {
101 auto_ptr<Botan::PK_Verifier> verifier(Botan::get_pk_verifier(*pub, "EMSA1(SHA-512)"));
102 verifier->update((const Botan::byte *)statement.data(), statement.size());
103 Botan::SecureVector<Botan::byte> sig;
104 sig.set((const Botan::byte *)signature.data(), signature.size());
105 return verifier->check_signature(sig);
108 // Private key functions
110 fides::privatekey::privatekey(): priv(0) {
113 fides::privatekey::~privatekey() {
118 void fides::privatekey::generate(const std::string &field) {
119 Botan::EC_Domain_Params domain = Botan::get_EC_Dom_Pars_by_oid(field);
120 pub = priv = new Botan::ECDSA_PrivateKey(rng, domain);
123 void fides::privatekey::generate(unsigned int bits) {
125 case 112: return generate("1.3.132.0.6");
126 case 128: return generate("1.3.132.0.28");
127 case 160: return generate("1.3.132.0.9");
128 case 192: return generate("1.3.132.0.31");
129 case 224: return generate("1.3.132.0.32");
130 case 256: return generate("1.3.132.0.10");
131 case 384: return generate("1.3.132.0.34");
132 case 521: return generate("1.3.132.0.35");
133 default: throw exception("Unsupported number of bits for private key");
137 void fides::privatekey::load_private(istream &in) {
139 Botan::DataSource_Stream stream(in);
140 pub = priv = dynamic_cast<Botan::ECDSA_PrivateKey *>(Botan::PKCS8::load_key(stream, rng, ""));
141 } catch(Botan::Exception &e) {
142 throw exception(e.what());
146 void fides::privatekey::load_private(const std::string &filename) {
147 ifstream in(filename.c_str());
151 void fides::privatekey::save_private(ostream &out) const {
152 out << Botan::PKCS8::PEM_encode(*priv);
155 void fides::privatekey::save_private(const std::string &filename) const {
156 ofstream out(filename.c_str());
160 string fides::privatekey::sign(const std::string &statement) const {
161 auto_ptr<Botan::PK_Signer> signer(Botan::get_pk_signer(*priv, "EMSA1(SHA-512)"));
162 Botan::SecureVector<Botan::byte> sig = signer->sign_message((const Botan::byte *)statement.data(), statement.size(), rng);
163 return string((const char *)sig.begin(), (size_t)sig.size());
166 // Base64 and hex encoding/decoding functions
168 string fides::hexencode(const string &in) {
169 Botan::Pipe pipe(new Botan::Hex_Encoder);
170 pipe.process_msg((Botan::byte *)in.data(), in.size());
171 return pipe.read_all_as_string();
174 string fides::hexdecode(const string &in) {
175 Botan::Pipe pipe(new Botan::Hex_Decoder);
176 pipe.process_msg((Botan::byte *)in.data(), in.size());
177 return pipe.read_all_as_string();
180 string fides::b64encode(const string &in) {
181 Botan::Pipe pipe(new Botan::Base64_Encoder);
182 pipe.process_msg((Botan::byte *)in.data(), in.size());
183 return pipe.read_all_as_string();
186 string fides::b64decode(const string &in) {
187 Botan::Pipe pipe(new Botan::Base64_Decoder);
188 pipe.process_msg((Botan::byte *)in.data(), in.size());
189 return pipe.read_all_as_string();
192 // Certificate functions
194 fides::certificate::certificate(const publickey *key, struct timeval timestamp, const std::string &statement, const std::string &signature): signer(key), timestamp(timestamp), statement(statement), signature(signature) {}
196 bool fides::certificate::validate() const {
197 string data = signer->fingerprint(256);
198 data += string((const char *)×tamp, sizeof timestamp);
200 return signer->verify(data, signature);
203 fides::certificate::certificate(const privatekey *key, struct timeval timestamp, const std::string &statement): signer(key), timestamp(timestamp), statement(statement) {
204 string data = signer->fingerprint(256);
205 data += string((const char *)×tamp, sizeof timestamp);
207 signature = key->sign(data);
210 string fides::certificate::fingerprint(unsigned int bits) const {
211 return signature.substr(signature.size() - bits / 8);
214 string fides::certificate::to_string() const {
215 string data = fides::hexencode(signer->fingerprint());
218 snprintf(ts, sizeof ts, "%lu.%06lu", timestamp.tv_sec, timestamp.tv_usec);
221 data += fides::b64encode(signature);
229 static vector<string> dirlist(const string &path) {
230 vector<string> files;
232 DIR *dir = opendir(path.c_str());
236 struct dirent entry, *result = &entry;
239 readdir_r(dir, &entry, &result);
243 if(result->d_type == DT_UNKNOWN) {
244 if(stat((path + "/" + result->d_name).c_str(), &st))
246 if(S_ISREG(st.st_mode))
247 files.push_back(result->d_name);
248 } else if(result->d_type == DT_REG) {
249 files.push_back(result->d_name);
258 void fides::certificate_save(const certificate *cert, const string &filename) const {
259 ofstream file(filename.c_str());
260 file << cert->to_string() << '\n';
263 fides::certificate *fides::certificate_load(const string &filename) {
264 ifstream file(filename.c_str());
267 return certificate_from_string(data);
270 fides::certificate *fides::certificate_from_string(const string &data) {
272 e = data.find(' ', 0);
273 if(e == string::npos)
274 throw exception("Invalid certificate");
275 string fingerprint = hexdecode(data.substr(0, e));
276 const publickey *signer = find_key(fingerprint);
278 throw exception("Unknown public key");
280 e = data.find('.', b);
281 if(e == string::npos)
282 throw exception("Invalid certificate");
283 struct timeval timestamp;
284 timestamp.tv_sec = atol(data.c_str() + b);
286 timestamp.tv_usec = atol(data.c_str() + b);
287 e = data.find(' ', b);
288 if(e == string::npos)
289 throw exception("Invalid certificate");
291 e = data.find(' ', b);
292 if(e == string::npos)
293 throw exception("Invalid certificate");
294 string signature = fides::b64decode(data.substr(b, e - b));
296 string statement = data.substr(b);
298 return new certificate(signer, timestamp, statement, signature);
301 // Fides main functions
303 fides::fides(const string &dir): homedir(dir) {
304 debug cerr << "Fides initialising\n";
306 // Set homedir to provided directory, or $FIDES_HOME, or $HOME/.fides, or as a last resort $PWD/.fides
308 homedir = getenv("FIDES_HOME") ?: "";
309 if(homedir.empty()) {
311 homedir = getenv("HOME") ?: getcwd(cwd, sizeof cwd);
312 homedir += "/.fides";
315 // Derived directories
317 certdir = homedir + "certs/";
318 keydir = homedir + "keys/";
319 obsoletedir = homedir + ".obsolete_certs/";
321 // Ensure the homedir and its subdirectories exist
322 mkdir(homedir.c_str(), 0700);
323 mkdir(certdir.c_str(), 0700);
324 mkdir(keydir.c_str(), 0700);
325 mkdir(obsoletedir.c_str(), 0700);
328 mykey.load_private(homedir + "priv");
330 } catch(fides::exception &e) {
331 cerr << "Fides generating keypair\n";
333 mykey.save_private(homedir + "priv");
334 mykey.save(keydir + hexencode(mykey.fingerprint()));
337 vector<string> files = dirlist(keydir);
338 for(size_t i = 0; i < files.size(); ++i) {
339 debug cerr << "Loading key " << files[i] << '\n';
341 publickey *key = new publickey();
342 key->load(keydir + files[i]);
343 keys[hexdecode(files[i])] = key;
346 keys[mykey.fingerprint()] = &mykey;
348 files = dirlist(certdir);
349 for(size_t i = 0; i < files.size(); ++i) {
350 debug cerr << "Loading certificate " << files[i] << '\n';
351 certificate *cert = certificate_load(certdir + files[i]);
352 if(false && !cert->validate()) {
353 cerr << "Bad certificate in database: " << cert->to_string() << '\n';
356 certs[hexdecode(files[i])] = cert;
359 // TODO: save and load this value
367 debug cerr << "Fides exitting\n";
368 for(map<string, certificate *>::const_iterator i = certs.begin(); i != certs.end(); ++i)
370 for(map<string, publickey *>::const_iterator i = keys.begin(); i != keys.end(); ++i)
371 if(i->second != &mykey)
375 bool fides::fsck() const {
378 for(map<string, certificate *>::const_iterator i = certs.begin(); i != certs.end(); ++i) {
379 if(!i->second->validate()) {
380 cerr << "Validation of certificate failed: " << i->second->to_string() << '\n';
385 cerr << errors << " errors in " << certs.size() << " certificates\n";
389 string fides::get_homedir() const {
393 bool fides::is_firstrun() const {
397 fides::publickey *fides::find_key(const string &fingerprint) const {
398 map<string, publickey *>::const_iterator i;
399 i = keys.find(fingerprint);
406 vector<const fides::certificate *> fides::find_certificates(const publickey *signer, const string ®ex) const {
407 vector<const certificate *> found;
408 map<string, certificate *>::const_iterator i;
409 regexp regexp(regex);
410 for(i = certs.begin(); i != certs.end(); ++i) {
412 cerr << "No certificate for " << hexencode(i->first) << '\n';
415 if(i->second->signer == signer)
416 if(regexp.match(i->second->statement))
417 found.push_back(i->second);
422 vector<const fides::certificate *> fides::find_certificates(const string ®ex) const {
423 vector<const certificate *> found;
424 map<string, certificate *>::const_iterator i;
425 regexp regexp(regex);
426 for(i = certs.begin(); i != certs.end(); ++i)
427 if(regexp.match(i->second->statement))
428 found.push_back(i->second);
432 vector<const fides::certificate *> fides::find_certificates(const publickey *signer) const {
433 vector<const certificate *> found;
434 map<string, certificate *>::const_iterator i;
435 for(i = certs.begin(); i != certs.end(); ++i)
436 if(i->second->signer == signer)
437 found.push_back(i->second);
441 void fides::import_all(istream &in) {
445 while(getline(in, line)) {
449 if(is_pem || !line.compare(0, 11, "-----BEGIN ")) {
451 if(!line.compare(0, 9, "-----END ")) {
452 fides::publickey *key = new publickey();
453 key->from_string(pem);
454 debug cerr << "Imported key " << hexencode(key->fingerprint()) << '\n';
463 fides::certificate *cert = certificate_from_string(line);
464 debug cerr << "Importing certificate " << hexencode(cert->fingerprint()) << '\n';
469 void fides::export_all(ostream &out) const {
470 for(map<string, publickey *>::const_iterator i = keys.begin(); i != keys.end(); ++i)
471 out << i->second->to_string();
472 for(map<string, certificate *>::const_iterator i = certs.begin(); i != certs.end(); ++i)
473 out << i->second->to_string() << '\n';
476 void fides::trust(const publickey *key) {
477 string full = "t+ " + hexencode(key->fingerprint());
481 void fides::distrust(const publickey *key) {
482 string full = "t- " + hexencode(key->fingerprint());
486 void fides::dctrust(const publickey *key) {
487 string full = "t0 " + hexencode(key->fingerprint());
491 void fides::update_trust() {
492 // clear trust on all keys
493 for(map<string, publickey *>::const_iterator i = keys.begin(); i != keys.end(); ++i)
494 i->second->trust = 0;
496 // Start by checking all trust certificates from ourself.
497 // If another key is positively or negatively trusted, update its trust score
498 // and add it to the the list of new keys to check.
499 // Then add our own key to the list of already checked keys.
500 // Then check all the trust certificates of those on the tocheck list, etc.
501 // Already checked keys are never updated anymore (TODO: is that smart?)
502 // Certificates of keys with a zero or negative trust score are not processed.
504 set<publickey *> checked;
505 set<publickey *> tocheck;
506 set<publickey *> newkeys;
507 set<publickey *>::iterator i;
510 tocheck.insert(&mykey);
512 while(tocheck.size()) {
514 checked.insert(tocheck.begin(), tocheck.end());
517 // loop over all keys whose certificates need to be checked
519 for(i = tocheck.begin(); i != tocheck.end(); ++i) {
520 debug cerr << "Trust for key " << hexencode((*i)->fingerprint()) << " set to " << (*i)->trust << '\n';
522 // except if this key is not trusted
527 // find all non-zero trust certificates of this key
529 vector<const certificate *> matches = find_certificates(*i, "^t[+-] ");
531 // update trust value of those keys
533 for(size_t j = 0; j < matches.size(); j++) {
534 publickey *other = find_key(hexdecode(matches[j]->statement.substr(3)));
537 cerr << "Trust certificate for unknown key: " << matches[j]->to_string() << '\n';
541 // except for keys we already checked
543 if(checked.find(other) != checked.end()) {
544 debug cerr << "Skipping trust certificate for already checked key: " << matches[j]->to_string() << '\n';
550 if(matches[j]->statement[1] == '+')
555 newkeys.insert(other);
563 void fides::merge(publickey *key) {
564 if(keys.find(key->fingerprint()) != keys.end()) {
565 debug cerr << "Key already known\n";
569 keys[key->fingerprint()] = key;
570 key->save(keydir + hexencode(key->fingerprint()));
573 void fides::merge(certificate *cert) {
574 // TODO: check if cert is already in database
575 // TODO: check if cert obsoletes other certs
577 // If we already know this certificate, drop it.
578 if(certs.find(cert->fingerprint()) != certs.end()) {
579 debug cerr << "Certificate already known\n";
583 // If the certificate does not validate, drop it.
584 if(!cert->validate()) {
585 // TODO: this should not happen, be wary of DoS attacks
586 cerr << "Trying to merge invalid certificate: " << cert->to_string() << '\n';
590 // TODO: move these regexps to the class?
591 regexp authexp("^a[+0-] ");
592 regexp trustexp("^t[+0-] ");
593 vector<const certificate *> others;
595 // Is this an authorisation cert?
596 if(authexp.match(cert->statement)) {
597 // Find certs identical except for the +/-/0
598 // TODO: escape statement in regexp
599 others = find_certificates(cert->signer, string("^a[+0-] ") + cert->statement.substr(3) + '$');
601 if(timercmp(&others[0]->timestamp, &cert->timestamp, >)) {
602 debug cerr << "Certificate is overruled by a newer certificate\n";
605 if(timercmp(&others[0]->timestamp, &cert->timestamp, ==)) {
606 // TODO: this should not happen, be wary of DoS attacks
607 debug cerr << "Certificate has same timestamp as another timestamp!\n";
610 debug cerr << "Certificate overrules an older certificate!\n";
611 // save new cert first
612 certificate_save(cert, certdir + hexencode(cert->fingerprint()));
613 certs[cert->fingerprint()] = cert;
616 rename((certdir + hexencode(others[0]->fingerprint())).c_str(), (obsoletedir + hexencode(others[0]->fingerprint())).c_str());
617 certs.erase(others[0]->fingerprint());
623 // Is this a trust cert?
624 // TODO: it's just the same as above!
625 if(trustexp.match(cert->statement)) {
626 // Find certs identical except for the +/-/0
627 // TODO: escape statement in regexp
628 others = find_certificates(cert->signer, string("^t[+0-] ") + cert->statement.substr(3) + '$');
630 if(timercmp(&others[0]->timestamp, &cert->timestamp, >)) {
631 debug cerr << "Certificate is overruled by a newer certificate\n";
634 if(timercmp(&others[0]->timestamp, &cert->timestamp, ==)) {
635 // TODO: this should not happen, be wary of DoS attacks
636 debug cerr << "Certificate has same timestamp as another timestamp!\n";
639 debug cerr << "Certificate overrules an older certificate!\n";
641 rename((certdir + hexencode(others[0]->fingerprint())).c_str(), (obsoletedir + hexencode(others[0]->fingerprint())).c_str());
642 certs.erase(others[0]->fingerprint());
644 certs[cert->fingerprint()] = cert;
645 certificate_save(cert, certdir + hexencode(cert->fingerprint()));
650 // Did somebody sign the exact same statement twice?
651 // Could happen if there is a different, conflicting statement between this new and the corresponding old one.
652 others = find_certificates(cert->signer, string("^") + cert->statement + '$');
654 if(timercmp(&others[0]->timestamp, &cert->timestamp, >)) {
655 debug cerr << "Certificate is overruled by a newer certificate\n";
658 if(timercmp(&others[0]->timestamp, &cert->timestamp, ==)) {
659 // TODO: this should not happen, be wary of DoS attacks
660 debug cerr << "Certificate has same timestamp as another timestamp!\n";
663 debug cerr << "Certificate overrules an older certificate!\n";
665 rename((certdir + hexencode(others[0]->fingerprint())).c_str(), (obsoletedir + hexencode(others[0]->fingerprint())).c_str());
666 certs.erase(others[0]->fingerprint());
668 certs[cert->fingerprint()] = cert;
669 certificate_save(cert, certdir + hexencode(cert->fingerprint()));
673 debug cerr << "Certificate is new\n";
674 certs[cert->fingerprint()] = cert;
675 certificate_save(cert, certdir + hexencode(cert->fingerprint()));
678 void fides::auth_stats(const string &statement, int &self, int &trusted, int &all) const {
679 self = trusted = all = 0;
680 vector<const certificate *> matches = find_certificates(string("^a[+0-] ") + statement + '$');
681 for(size_t i = 0; i < matches.size(); ++i) {
682 char code = matches[i]->statement[1];
688 if(matches[i]->signer == &mykey)
690 if(matches[i]->signer->trust > 0)
696 bool fides::is_trusted(const publickey *key) const {
697 return key->trust > 0;
700 bool fides::is_distrusted(const publickey *key) const {
701 return key->trust < 0;
704 bool fides::is_allowed(const string &statement, const publickey *key) const {
705 int self, trusted, all;
708 auth_stats(hexencode(key->fingerprint()) + " " + statement, self, trusted, all);
710 auth_stats(statement, self, trusted, all);
720 bool fides::is_denied(const string &statement, const publickey *key) const {
721 int self, trusted, all;
724 auth_stats(hexencode(key->fingerprint()) + " " + statement, self, trusted, all);
726 auth_stats(statement, self, trusted, all);
736 void fides::sign(const string &statement) {
737 // Try to set "latest" to now, but ensure monoticity
739 gettimeofday(&now, 0);
740 if(timercmp(&latest, &now, >=)) {
742 if(latest.tv_usec >= 1000000) {
744 latest.tv_usec -= 1000000;
750 // Create a new certificate and merge it with our database
751 merge(new certificate(&mykey, latest, statement));
754 void fides::allow(const string &statement, const publickey *key) {
757 full += hexencode(key->fingerprint()) + ' ';
762 void fides::dontcare(const string &statement, const publickey *key) {
765 full += hexencode(key->fingerprint()) + ' ';
770 void fides::deny(const string &statement, const publickey *key) {
773 full += hexencode(key->fingerprint()) + ' ';