/* trustdb.c * Copyright (c) 1997 by Werner Koch (dd9jn) * * This file is part of G10. * * G10 is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * G10 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include #include #include #include "errors.h" #include "iobuf.h" #include "keydb.h" #include "memory.h" #include "util.h" #include "trustdb.h" #include "options.h" #define TRUST_RECORD_LEN 40 #define SIGS_PER_RECORD ((TRUST_RECORD_LEN-10)/5) struct trust_record { byte rectype; byte reserved; union { struct { /* version record: */ byte magic[2]; byte version; /* should be 1 */ byte reserved[3]; u32 locked; /* pid of process which holds a lock */ u32 created; /* timestamp of trustdb creation */ u32 modified; /* timestamp of last modification */ u32 validated; /* timestamp of last validation */ u32 local_id_counter; byte marginals_needed; byte completes_needed; byte max_cert_depth; } version; struct { /* public key record */ u32 local_id; u32 keyid[2]; byte algo; byte reserved; byte fingerprint[20]; byte ownertrust; /* fixme: indicate a flag to */ } pubkey; struct { /* cache record */ u32 owner; u32 keyid[2]; /* needed?? */ byte valid; byte reserved; byte blockhash[20]; byte n_untrusted; byte n_marginal; byte n_fully; byte trustlevel; } cache; struct { u32 owner; /* local_id of record owner (pubkey record) */ u32 chain; /* offset of next record or NULL for last one */ struct { u32 local_id; /* of pubkey record of signator (0=unused) */ byte flag; /* reserved */ } sig[SIGS_PER_RECORD]; } sigrec; } r; }; typedef struct trust_record TRUSTREC; static void create_db( const char *fname ); static void open_db(void); static int read_record( u32 recnum, TRUSTREC *rec ); static u32 new_local_id(void); static char *db_name; static int db_fd = -1; static int no_io_dbg = 0; #define buftou32( p ) ((*(byte*)(p) << 24) | (*((byte*)(p)+1)<< 16) | \ (*((byte*)(p)+2) << 8) | (*((byte*)(p)+3))) #define buftou16( p ) ((*((byte*)(p)) << 8) | (*((byte*)(p)+1))) #define u32tobuf( p, a ) do { \ ((byte*)p)[0] = a >> 24; \ ((byte*)p)[1] = a >> 16; \ ((byte*)p)[2] = a >> 8; \ ((byte*)p)[3] = a ; \ } while(0) #define u16tobuf( p, a ) do { \ ((byte*)p)[0] = a >> 8; \ ((byte*)p)[1] = a ; \ } while(0) /************************************************** ************** read and write helpers ************ **************************************************/ static void fwrite_8(FILE *fp, byte a) { if( putc( a & 0xff, fp ) == EOF ) log_fatal("error writing byte to trustdb: %s\n", strerror(errno) ); } static void fwrite_16(FILE *fp, u16 a) { putc( (a>>8) & 0x0ff , fp ); if( putc( a & 0xff, fp ) == EOF ) log_fatal("error writing u16 to trustdb: %s\n", strerror(errno) ); } static int fwrite_32( FILE*fp, u32 a) { putc( (a>>24) & 0xff, fp ); putc( (a>>16) & 0xff, fp ); putc( (a>> 8) & 0xff, fp ); if( putc( a & 0xff, fp ) == EOF ) log_fatal("error writing u32 to trustdb: %s\n", strerror(errno) ); } static int fwrite_zeros( FILE *fp, size_t n) { while( n-- ) if( putc( 0, fp ) == EOF ) log_fatal("error writing zeros to trustdb: %s\n", strerror(errno) ); } /************************************************** ************** read and write stuff ************** **************************************************/ /**************** * Create a new trustdb */ static void create_db( const char *fname ) { FILE *fp; u32 along; u16 ashort; fp =fopen( fname, "w" ); if( !fp ) log_fatal("can't create %s: %s\n", fname, strerror(errno) ); fwrite_8( fp, 1 ); fwrite_8( fp, 'g' ); fwrite_8( fp, '1' ); fwrite_8( fp, '0' ); fwrite_8( fp, 1 ); /* version */ fwrite_zeros( fp, 3 ); /* reserved */ fwrite_32( fp, 0 ); /* not locked */ fwrite_32( fp, make_timestamp() ); /* created */ fwrite_32( fp, 0 ); /* not yet modified */ fwrite_32( fp, 0 ); /* not yet validated*/ fwrite_32( fp, 0 ); /* local-id-counter (not used) */ fwrite_8( fp, 3 ); /* marginals needed */ fwrite_8( fp, 1 ); /* completes needed */ fwrite_8( fp, 4 ); /* max_cet_depth */ fwrite_zeros( fp, 9 ); /* filler */ fclose(fp); } static void open_db() { TRUSTREC rec; assert( db_fd == -1 ); db_fd = open( db_name, O_RDWR ); if( db_fd == -1 ) log_fatal("can't open %s: %s\n", db_name, strerror(errno) ); if( read_record( 0, &rec ) ) log_fatal("TrustDB %s is invalid\n", db_name ); /* fixme: check ->locked and other stuff */ } /**************** * read the record with number recnum * returns: -1 on error, 0 on success */ static int read_record( u32 recnum, TRUSTREC *rec ) { byte buf[TRUST_RECORD_LEN], *p; int rc = 0; int n, i; if( db_fd == -1 ) open_db(); if( DBG_TRUST && !no_io_dbg ) log_debug("trustdb: read_record(%lu)\n", (ulong)recnum); if( lseek( db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET ) == -1 ) { log_error("trustdb: lseek failed: %s\n", strerror(errno) ); return G10ERR_READ_FILE; } n = read( db_fd, buf, TRUST_RECORD_LEN); if( !n ) { if( DBG_TRUST ) log_debug("trustdb: no record at %lu\n", (ulong)recnum ); return -1; /* eof */ } else if( n != TRUST_RECORD_LEN ) { log_error("trustdb: read failed (n=%d): %s\n", n, strerror(errno) ); return G10ERR_READ_FILE; } p = buf; rec->rectype = *p++; rec->reserved = *p++; switch( rec->rectype ) { case 0: /* unused record */ break; case 1: /* version record */ rec->r.version.magic[0] = *p++; rec->r.version.magic[1] = *p++; rec->r.version.version = *p++; memcpy( rec->r.version.reserved, p, 3); p += 3; rec->r.version.locked = buftou32(p); p += 4; rec->r.version.created = buftou32(p); p += 4; rec->r.version.modified = buftou32(p); p += 4; rec->r.version.validated= buftou32(p); p += 4; rec->r.version.local_id_counter = buftou32(p); p += 4; rec->r.version.marginals_needed = *p++; rec->r.version.completes_needed = *p++; rec->r.version.max_cert_depth = *p++; if( recnum ) { log_error("%s: version record with recnum %lu\n", db_name, (ulong)recnum ); rc = G10ERR_TRUSTDB; } if( rec->reserved != 'g' || rec->r.version.magic[0] != '1' || rec->r.version.magic[1] != '0' ) { log_error("%s: not a trustdb file\n", db_name ); rc = G10ERR_TRUSTDB; } if( rec->r.version.version != 1 ) { log_error("%s: invalid file version %d\n", db_name, rec->r.version.version ); rc = G10ERR_TRUSTDB; } break; case 2: rec->r.pubkey.local_id = buftou32(p); p += 4; rec->r.pubkey.keyid[0] = buftou32(p); p += 4; rec->r.pubkey.keyid[1] = buftou32(p); p += 4; rec->r.pubkey.algo = *p++; rec->r.pubkey.reserved = *p++; memcpy( rec->r.pubkey.fingerprint, p, 20); p += 20; rec->r.pubkey.ownertrust = *p++; if( rec->r.pubkey.local_id != recnum ) { log_error("%s: pubkey local_id != recnum (%lu,%lu)\n", db_name, (ulong)rec->r.pubkey.local_id, (ulong)recnum ); rc = G10ERR_TRUSTDB; } break; case 3: rec->r.cache.local_id = buftou32(p); p += 4; rec->r.cache.keyid[0] = buftou32(p); p += 4; rec->r.cache.keyid[1] = buftou32(p); p += 4; rec->r.cache.valid = *p++; rec->r.cache.reserved = *p++; memcpy(rec->r.cache.blockhash, p, 20); p += 20; rec->r.cache.n_untrusted = *p++; rec->r.cache.n_marginal = *p++; rec->r.cache.n_fully = *p++; rec->r.cache.trustlevel = *p++; break; case 4: case 5: rec->r.sigrec.owner = buftou32(p); p += 4; rec->r.sigrec.chain = buftou32(p); p += 4; for(i=0; i < SIGS_PER_RECORD; i++ ) { rec->r.sigrec.sig[i].local_id = buftou32(p); p += 4; rec->r.sigrec.sig[i].flag = *p++; } break; default: log_error("%s: invalid record type %d at recnum %lu\n", db_name, rec->rectype, (ulong)recnum ); rc = G10ERR_TRUSTDB; break; } return rc; } /**************** * Write the record at RECNUM */ static int write_record( u32 recnum, TRUSTREC *rec ) { byte buf[TRUST_RECORD_LEN], *p; int rc = 0; int i, n; if( db_fd == -1 ) open_db(); if( DBG_TRUST && !no_io_dbg ) log_debug("trustdb: write_record(%lu)\n", (ulong)recnum); memset(buf, 0, TRUST_RECORD_LEN); p = buf; *p++ = rec->rectype; *p++ = rec->reserved; switch( rec->rectype ) { case 0: /* unused record */ break; case 1: /* version record */ log_bug(NULL); break; case 2: u32tobuf(p, rec->r.pubkey.local_id); p += 4; u32tobuf(p, rec->r.pubkey.keyid[0]); p += 4; u32tobuf(p, rec->r.pubkey.keyid[1]); p += 4; *p++ = rec->r.pubkey.algo; *p++ = rec->r.pubkey.reserved; memcpy( p, rec->r.pubkey.fingerprint, 20); p += 20; *p++ = rec->r.pubkey.ownertrust; assert( rec->r.pubkey.local_id == recnum ); break; case 3: u32tobuf(p, rec->r.cache.local_id); p += 4; u32tobuf(p, rec->r.cache.keyid[0]); p += 4; u32tobuf(p, rec->r.cache.keyid[1]); p += 4; *p++ = rec->r.cache.valid; *p++ = rec->r.cache.reserved; memcpy(p, rec->r.cache.blockhash, 20); p += 20; *p++ = rec->r.cache.n_untrusted; *p++ = rec->r.cache.n_marginal; *p++ = rec->r.cache.n_fully; *p++ = rec->r.cache.trustlevel; break; case 4: case 5: u32tobuf(p, rec->r.sigrec.owner); p += 4; u32tobuf(p, rec->r.sigrec.chain); p += 4; for(i=0; i < SIGS_PER_RECORD; i++ ) { u32tobuf(p, rec->r.sigrec.sig[i].local_id); p += 4; *p++ = rec->r.sigrec.sig[i].flag; } break; default: log_bug(NULL); } if( lseek( db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET ) == -1 ) { log_error("trustdb: lseek failed: %s\n", strerror(errno) ); return G10ERR_WRITE_FILE; } n = write( db_fd, buf, TRUST_RECORD_LEN); if( n != TRUST_RECORD_LEN ) { log_error("trustdb: write failed (n=%d): %s\n", n, strerror(errno) ); return G10ERR_WRITE_FILE; } return rc; } static u32 new_local_id() { off_t offset; u32 recnum; /* fixme: look for unused records */ offset = lseek( db_fd, 0, SEEK_END ); if( offset == -1 ) log_fatal("trustdb: lseek to end failed: %s\n", strerror(errno) ); recnum = offset / TRUST_RECORD_LEN; assert(recnum); /* this is will never be the first record */ return recnum ; } /**************** * Scan the trustdb for a record of type RECTYPE which matches PKC * The local_id is set to the correct value */ static int scan_record_by_pkc( PKT_public_cert *pkc, TRUSTREC *rec, int rectype ) { u32 recnum; u32 keyid[2]; byte *fingerprint; size_t fingerlen; int dbg = DBG_TRUST; int rc; assert( rectype == 2 || rectype == 3 ); if( DBG_TRUST ) log_debug("trustdb: scan_record_by_pkc\n"); keyid_from_pkc( pkc, keyid ); fingerprint = fingerprint_from_pkc( pkc, &fingerlen ); assert( fingerlen == 20 || fingerlen == 16 ); no_io_dbg = 1; for(recnum=1; !(rc=read_record( recnum, rec)); recnum++ ) { if( rec->rectype != rectype ) continue; if( rec->rectype == 2 ) { if( rec->r.pubkey.keyid[0] == keyid[0] && rec->r.pubkey.keyid[1] == keyid[1] && rec->r.pubkey.algo == pkc->pubkey_algo && !memcmp(rec->r.pubkey.fingerprint, fingerprint, fingerlen) ) { /* found */ /* store the local_id */ if( pkc->local_id && pkc->local_id != recnum ) log_error("%s: found record, but local_id from mem does " "not match recnum (%lu,%lu)\n", db_name, (ulong)pkc->local_id, (ulong)recnum ); pkc->local_id = recnum; no_io_dbg = 0; return 0; } } else log_bug("not yet implemented\n"); } no_io_dbg = 0; if( DBG_TRUST ) log_debug("trustdb: scan_record_by_pkc: %s\n", rc==-1?"eof": g10_errstr(rc)); if( rc != -1 ) log_error("%s: scan_record_by_pkc failed: %s\n",db_name, g10_errstr(rc) ); return rc; } /**************** * scan the DB for a record of type RECTYPE which can be localized * with LOCAL_ID */ static int scan_record( u32 local_id, TRUSTREC *rec, int rectype, u32 *r_recnum ) { u32 recnum; u32 keyid[2]; int dbg = DBG_TRUST; int rc; assert( rectype == 3 || rectype == 4 ); if( DBG_TRUST ) log_debug("trustdb: scan_record type %d local_id %lu\n", rectype, (ulong)local_id); no_io_dbg = 1; for(recnum=1; !(rc=read_record( recnum, rec)); recnum++ ) { if( rec->rectype != rectype ) continue; if( rec->rectype == 34 ) { if( rec->r.cache.owner == local_id ) { /* found */ *r_recnum = recnum; no_io_dbg = 0; return 0; } } else if( rec->rectype == 4 ) { if( rec->r.sigrec.owner == local_id ) { /* found */ *r_recnum = recnum; no_io_dbg = 0; return 0; } } else log_bug("not yet implemented\n"); } no_io_dbg = 0; if( DBG_TRUST ) log_debug("trustdb: scan_record: %s\n", rc==-1?"eof": g10_errstr(rc)); if( rc != -1 ) log_error("%s: scan_record failed: %s\n",db_name, g10_errstr(rc) ); return rc; } /*********************************************** ************* trust logic ******************* ***********************************************/ /**************** * Verify, that all our public keys are in the trustDB and marked as * ultimately trusted. */ static int verify_own_certs() { int rc; void *enum_context = NULL; PKT_secret_cert *skc = m_alloc_clear( sizeof *skc ); PKT_public_cert *pkc = m_alloc_clear( sizeof *pkc ); u32 keyid[2]; int trust; while( !(rc=enum_secret_keys( &enum_context, skc) ) ) { /* fixme: to be sure that it is a secret key of our own, * we should check it, but this needs a passphrase * for every key and this boring for the user. * Solution: Sign the secring and the trustring * and verify this signature during * startup */ keyid_from_skc( skc, keyid ); if( DBG_TRUST ) log_debug("checking secret key %08lX\n", (ulong)keyid[1] ); /* look wether we can access the public key of this secret key */ rc = get_pubkey( pkc, keyid ); if( rc ) { log_error("keyid %08lX: secret key without public key\n", (ulong)keyid[1] ); goto leave; } if( cmp_public_secret_cert( pkc, skc ) ) { log_error("keyid %08lX: secret and public key don't match\n", (ulong)keyid[1] ); rc = G10ERR_GENERAL; goto leave; } /* look into the trustdb */ rc = check_pkc_trust( pkc, &trust ); if( rc ) { log_info("keyid %08lX: problem in trustdb: %s\n", (ulong)keyid[1], g10_errstr(rc) ); goto leave; } if( trust & TRUST_NO_PUBKEY ) { log_info("keyid %08lX: not yet in trustdb\n", (ulong)keyid[1] ); /* FIXME: insert */ } else if( (trust & TRUST_MASK) != TRUST_ULT_TRUST ) { log_error("keyid %08lX: not marked as ultimately trusted\n", (ulong)keyid[1] ); /* FIXME: mark */ } release_secret_cert_parts( skc ); release_public_cert_parts( pkc ); } if( rc != -1 ) log_error("enum_secret_keys failed: %s\n", g10_errstr(rc) ); else rc = 0; leave: free_secret_cert( skc ); free_public_cert( pkc ); return rc; } /**************** * Check all the sigs of the given keyblock and mark them * as checked. */ static int check_sigs( KBNODE keyblock ) { KBNODE kbctx; KBNODE node; int rc; for( kbctx=NULL; (node=walk_kbtree( keyblock, &kbctx)) ; ) { if( node->pkt->pkttype == PKT_SIGNATURE && (node->pkt->pkt.signature->sig_class&~3) == 0x10 ) { PKT_signature *sig = node->pkt->pkt.signature; rc = check_key_signature( keyblock, node ); if( !rc ) node->flag |= 1; /* mark signature valid */ if( DBG_TRUST ) log_debug("trustdb: sig from %08lX: %s\n", rc? g10_errstr(rc): "okay" ); } } return 0; } /**************** * If we do not have sigrecs for the given key, build them and write them * to the trustdb */ static int build_sigrecs( KBNODE keyblock ) { } /**************** * Recursive check the signatures. */ static int walk( KBNODE keyblock, int levels ) { KBNODE kbctx, node; check_sigs( keyblock ); if( levels ) { /* check the next level */ for( kbctx=NULL; (node=walk_kbtree( keyblock, &kbctx)) ; ) { if( node->pkt->pkttype == PKT_SIGNATURE && (node->flag & 1) ) { /* read the keyblock for this signator */ /* and check his signatures */ /*walk( his_keyblock, levels-1)*/ } } } } /**************** * */ static int check_trust() { /* check the ca } /********************************************************* **************** API Interface ************************ *********************************************************/ /**************** * Perform some checks over the trustdb * level 0: used for initial program startup */ int check_trustdb( int level ) { int rc=0; if( !level ) { char *fname = make_filename("~/.g10", "trustDB", NULL ); if( access( fname, R_OK ) ) { if( errno != ENOENT ) { log_error("can't access %s: %s\n", fname, strerror(errno) ); m_free(fname); return G10ERR_TRUSTDB; } create_db( fname ); } m_free(db_name); db_name = fname; /* we can verify a signature about our local data (secring and trustdb) * in ~/.g10/ here */ rc = verify_private_data(); if( !rc ) { /* verify, that our own certificates are in the trustDB * or move them to the trustdb. */ rc = verify_own_certs(); /* should we check wether there is no other ultimately trusted * key in the database? */ } } else log_bug(NULL); return rc; } /**************** * Get the trustlevel for this PKC. * Note: This does not ask any questions * Returns: 0 okay of an errorcode * * It operates this way: * locate the pkc in the trustdb * found: * Do we have a valid cache record for it? * yes: return trustlevel from cache * no: make a cache record and all the other stuff * not found: * Return with a trustlevel, saying that we do not have * a trust record for it. The caller may use insert_trust_record() * and then call this function here again. * * Problems: How do we get the complete keyblock to check that the * cache record is actually valid? Think we need a clever * cache in getkey.c to keep track of this stuff. Maybe it * is not necessary to check this if we use a local pubring. Hmmmm. */ int check_pkc_trust( PKT_public_cert *pkc, int *r_trustlevel ) { TRUSTREC rec; int trustlevel = 0; int rc=0; if( opt.verbose ) log_info("check_pkc_trust() called.\n"); /* get the pubkey record */ if( pkc->local_id ) { if( read_record( pkc->local_id, &rec ) ) { log_error("check_pkc_trust: read record failed\n"); return G10ERR_TRUSTDB; } } else { /* no local_id: scan the trustdb */ if( (rc=scan_record_by_pkc( pkc, &rec, 2 )) && rc != -1 ) { log_error("check_pkc_trust: scan_record_by_pkc(2) failed: %s\n", g10_errstr(rc)); return G10ERR_TRUSTDB; } else if( rc == -1 ) { log_error("check_pkc_trust: pubkey not in TrustDB\n"); trustlevel = TRUST_NO_PUBKEY; goto leave; } } /* fixme: do some additional checks on the pubkey record */ /* see wether we have a cache record */ leave: if( opt.verbose ) log_info("check_pkc_trust() returns trustlevel %04x.\n", trustlevel); *r_trustlevel = trustlevel; return 0; } /**************** * Insert a trust record into the TrustDB * This function failes if this record already exists. */ int insert_trust_record( PKT_public_cert *pkc ) { TRUSTREC rec; u32 keyid[2]; u32 recnum; byte *fingerprint; size_t fingerlen; if( DBG_TRUST ) log_debug("trustdb: insert_record\n"); assert( !pkc->local_id ); keyid_from_pkc( pkc, keyid ); fingerprint = fingerprint_from_pkc( pkc, &fingerlen ); /* FIXME: check that we do not have this record. */ recnum = new_local_id(); /* build record */ memset( &rec, 0, sizeof rec ); rec.rectype = 2; /* the pubkey record */ rec.r.pubkey.local_id = recnum; rec.r.pubkey.keyid[0] = keyid[0]; rec.r.pubkey.keyid[1] = keyid[1]; rec.r.pubkey.algo = pkc->pubkey_algo; memcpy(rec.r.pubkey.fingerprint, fingerprint, fingerlen ); rec.r.pubkey.ownertrust = 0; if( write_record( recnum, &rec ) ) { log_error("insert_trust_record: write failed\n"); return G10ERR_TRUSTDB; } pkc->local_id = recnum; return 0; } int update_trust_record( PKT_public_cert *pkc, int new_trust ) { TRUSTREC rec; u32 keyid[2]; u32 recnum; if( DBG_TRUST ) log_debug("trustdb: update_record\n"); assert( pkc->local_id ); if( read_record( pkc->local_id, &rec ) ) { log_error("update_trust_record: read failed\n"); return G10ERR_TRUSTDB; } /* check keyid, fingerprint etc ? */ rec.r.pubkey.ownertrust = 0; if( write_record( recnum, &rec ) ) { log_error("insert_trust_record: write failed\n"); return G10ERR_TRUSTDB; } return 0; } int verify_private_data() { int rc = 0; char *sigfile = make_filename("~/.g10", "sig", NULL ); if( access( sigfile, R_OK ) ) { if( errno != ENOENT ) { log_error("can't access %s: %s\n", sigfile, strerror(errno) ); rc = G10ERR_TRUSTDB; goto leave; } log_info("private data signature missing; creating ...\n"); rc = sign_private_data(); if( rc ) { log_error("error creating %s: %s\n", sigfile, g10_errstr(rc) ); goto leave; } } /* FIXME: verify this signature */ leave: m_free(sigfile); return rc; } int sign_private_data() { int rc; char *sigfile = make_filename("~/.g10", "sig", NULL ); char *secring = make_filename("~/.g10", "secring.g10", NULL ); STRLIST list = NULL; add_to_strlist( &list, db_name ); add_to_strlist( &list, secring ); rc = sign_file( list, 1, NULL, 0, NULL, sigfile); m_free(sigfile); m_free(secring); free_strlist(list); return rc; }