diff options
Diffstat (limited to 'g10/trustdb.c')
-rw-r--r-- | g10/trustdb.c | 588 |
1 files changed, 582 insertions, 6 deletions
diff --git a/g10/trustdb.c b/g10/trustdb.c index 46c950021..1dfaaa570 100644 --- a/g10/trustdb.c +++ b/g10/trustdb.c @@ -25,6 +25,7 @@ #include <unistd.h> #include <errno.h> #include <assert.h> +#include <fcntl.h> #include "errors.h" #include "iobuf.h" @@ -41,7 +42,6 @@ struct trust_record { byte rectype; byte reserved; union { - byte raw[TRUST_RECORD_LEN-2]; struct { /* version record: */ byte magic[2]; byte version; /* should be 1 */ @@ -76,10 +76,32 @@ struct trust_record { } cache; } 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 ************ @@ -147,7 +169,7 @@ create_db( const char *fname ) 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 */ + 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 */ @@ -155,14 +177,255 @@ create_db( const char *fname ) 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; + + 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; + 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 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; + 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 maches PKC + * The local_id is set to the correct value + */ +static int +scan_record( 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\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: eof or error\n"); + if( rc != -1 ) + log_error("%s: scan_record failed: %s\n",db_name, g10_errstr(rc) ); + return rc; +} @@ -171,8 +434,151 @@ create_db( const char *fname ) ************* 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; +} + + +/**************** + * 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() +{ +} + /********************************************************* @@ -181,11 +587,13 @@ create_db( const char *fname ) /**************** * Perform some checks over the trustdb - * level 0: used on initial program startup + * 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 ) ) { @@ -200,13 +608,22 @@ check_trustdb( int level ) db_name = fname; /* we can verify a signature about our local data (secring and trustdb) - * in ~/.g10/ here - */ + * 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 0; + return rc; } @@ -234,12 +651,171 @@ check_trustdb( int level ) 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( pkc, &rec, 2 )) && rc != -1 ) { + log_error("check_pkc_trust: scan_record(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 */ + + + 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 ? */ + + 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 +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; +} + |