/* trustdb.c * Copyright (C) 1998 Free Software Foundation, Inc. * * This file is part of GNUPG. * * GNUPG 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. * * GNUPG 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 #include #include #include "errors.h" #include "iobuf.h" #include "keydb.h" #include "memory.h" #include "util.h" #include "trustdb.h" #include "options.h" #include "packet.h" #include "main.h" #include "i18n.h" #include "tdbio.h" #if MAX_FINGERPRINT_LEN > 20 #error Must change structure of trustdb #endif struct local_id_item { struct local_id_item *next; ulong lid; unsigned flag; }; struct local_id_table { struct local_id_table *next; /* only used to keep a list of unused tables */ struct local_id_item *items[16]; }; typedef struct local_id_table *LOCAL_ID_TABLE; typedef struct trust_info TRUST_INFO; struct trust_info { ulong lid; byte otrust; /* ownertrust (assigned trust) */ byte trust; /* calculated trust (validity) */ }; typedef struct trust_seg_list *TRUST_SEG_LIST; struct trust_seg_list { TRUST_SEG_LIST next; int pathlen; TRUST_INFO path[1]; }; struct recno_list_struct { struct recno_list_struct *next; ulong recno; int type; }; typedef struct recno_list_struct *RECNO_LIST; static int walk_sigrecs( SIGREC_CONTEXT *c ); static LOCAL_ID_TABLE new_lid_table(void); static void release_lid_table( LOCAL_ID_TABLE tbl ); static int ins_lid_table_item( LOCAL_ID_TABLE tbl, ulong lid, unsigned flag ); static int qry_lid_table_flag( LOCAL_ID_TABLE tbl, ulong lid, unsigned *flag ); static void print_user_id( const char *text, u32 *keyid ); static int list_sigs( ulong pubkey_id ); static int do_check( TRUSTREC *drec, unsigned *trustlevel ); static int get_dir_record( PKT_public_key *pk, TRUSTREC *rec ); static void upd_pref_record( TRUSTREC *urec, u32 *keyid, PKT_signature *sig ); static void upd_cert_record( KBNODE keyblock, KBNODE signode, u32 *keyid, TRUSTREC *drec, RECNO_LIST *recno_list, int recheck, TRUSTREC *urec, const byte *uidhash, int revoke ); /* a table used to keep track of ultimately trusted keys * which are the ones from our secrings */ static LOCAL_ID_TABLE ultikey_table; /* list of unused lid items and tables */ static LOCAL_ID_TABLE unused_lid_tables; static struct local_id_item *unused_lid_items; #define HEXTOBIN(a) ( (a) >= '0' && (a) <= '9' ? ((a)-'0') : \ (a) >= 'A' && (a) <= 'F' ? ((a)-'A'+10) : ((a)-'a'+10)) /********************************************** *********** record read write ************** **********************************************/ static void die_invalid_db() { log_error(_( "The trust DB is corrupted; please run \"gpgm --fix-trust-db\".\n") ); g10_exit(2); } /**************** * Read a record but die if it does not exist */ static void read_record( ulong recno, TRUSTREC *rec, int rectype ) { int rc = tdbio_read_record( recno, rec, rectype ); if( !rc ) return; log_error(_("trust record %lu, req type %d: read failed: %s\n"), recno, rectype, g10_errstr(rc) ); die_invalid_db(); } /**************** * Wirte a record but die on error */ static void write_record( TRUSTREC *rec ) { int rc = tdbio_write_record( rec ); if( !rc ) return; log_error(_("trust record %lu, type %d: write failed: %s\n"), rec->recnum, rec->rectype, g10_errstr(rc) ); die_invalid_db(); } /**************** * Delete a record but die on error */ static void delete_record( ulong recno ) { int rc = tdbio_delete_record( recno ); if( !rc ) return; log_error(_("trust record %lu: delete failed: %s\n"), recno, g10_errstr(rc) ); die_invalid_db(); } /**************** * sync the db */ static void do_sync( ) { int rc = tdbio_sync(); if( !rc ) return; log_error(_("trust db: sync failed: %s\n"), g10_errstr(rc) ); g10_exit(2); } /********************************************** ************* list helpers ******************* **********************************************/ /**************** * Insert a new item into a recno list */ static void ins_recno_list( RECNO_LIST *head, ulong recno, int type ) { RECNO_LIST item = m_alloc( sizeof *item ); item->recno = recno; item->type = type; item->next = *head; *head = item; } static RECNO_LIST qry_recno_list( RECNO_LIST list, ulong recno, int type ) { for( ; list; list = list->next ) { if( list->recno == recno && (!type || list->type == type) ) return list; } return NULL; } static void rel_recno_list( RECNO_LIST *head ) { RECNO_LIST r, r2; for(r = *head; r; r = r2 ) { r2 = r->next; m_free(r); } *head = NULL; } static LOCAL_ID_TABLE new_lid_table(void) { LOCAL_ID_TABLE a; a = unused_lid_tables; if( a ) { unused_lid_tables = a->next; memset( a, 0, sizeof *a ); } else a = m_alloc_clear( sizeof *a ); return a; } static void release_lid_table( LOCAL_ID_TABLE tbl ) { struct local_id_item *a, *a2; int i; for(i=0; i < 16; i++ ) { for(a=tbl->items[i]; a; a = a2 ) { a2 = a->next; a->next = unused_lid_items; unused_lid_items = a; } } tbl->next = unused_lid_tables; unused_lid_tables = tbl; } /**************** * Add a new item to the table or return 1 if we already have this item */ static int ins_lid_table_item( LOCAL_ID_TABLE tbl, ulong lid, unsigned flag ) { struct local_id_item *a; for( a = tbl->items[lid & 0x0f]; a; a = a->next ) if( a->lid == lid ) return 1; a = unused_lid_items; if( a ) unused_lid_items = a->next; else a = m_alloc( sizeof *a ); a->lid = lid; a->flag = flag; a->next = tbl->items[lid & 0x0f]; tbl->items[lid & 0x0f] = a; return 0; } static int qry_lid_table_flag( LOCAL_ID_TABLE tbl, ulong lid, unsigned *flag ) { struct local_id_item *a; for( a = tbl->items[lid & 0x0f]; a; a = a->next ) if( a->lid == lid ) { if( flag ) *flag = a->flag; return 0; } return -1; } /**************** * Return the keyid from the primary key identified by LID. */ int keyid_from_lid( ulong lid, u32 *keyid ) { TRUSTREC rec; int rc; rc = tdbio_read_record( lid, &rec, 0 ); if( rc ) { log_error(_("error reading dir record for LID %lu: %s\n"), lid, g10_errstr(rc)); return G10ERR_TRUSTDB; } if( rec.rectype == RECTYPE_SDIR ) return 0; if( rec.rectype != RECTYPE_DIR ) { log_error(_("lid %lu: expected dir record, got type %d\n"), lid, rec.rectype ); return G10ERR_TRUSTDB; } if( !rec.r.dir.keylist ) { log_error(_("no primary key for LID %lu\n"), lid ); return G10ERR_TRUSTDB; } rc = tdbio_read_record( rec.r.dir.keylist, &rec, RECTYPE_KEY ); if( rc ) { log_error(_("error reading primary key for LID %lu: %s\n"), lid, g10_errstr(rc)); return G10ERR_TRUSTDB; } keyid_from_fingerprint( rec.r.key.fingerprint, rec.r.key.fingerprint_len, keyid ); return 0; } ulong lid_from_keyblock( KBNODE keyblock ) { KBNODE node = find_kbnode( keyblock, PKT_PUBLIC_KEY ); PKT_public_key *pk; if( !node ) BUG(); pk = node->pkt->pkt.public_key; if( !pk->local_id ) { TRUSTREC rec; get_dir_record( pk, &rec ); } return pk->local_id; } /**************** * Walk through the signatures of a public key. * The caller must provide a context structure, with all fields set * to zero, but the local_id field set to the requested key; * This function does not change this field. On return the context * is filled with the local-id of the signature and the signature flag. * No fields should be changed (clearing all fields and setting * pubkeyid is okay to continue with an other pubkey) * Returns: 0 - okay, -1 for eof (no more sigs) or any other errorcode */ static int walk_sigrecs( SIGREC_CONTEXT *c ) { TRUSTREC *r; ulong rnum; if( c->ctl.eof ) return -1; r = &c->ctl.rec; if( !c->ctl.init_done ) { c->ctl.init_done = 1; read_record( c->lid, r, 0 ); if( r->rectype != RECTYPE_DIR ) { c->ctl.eof = 1; return -1; /* return eof */ } c->ctl.nextuid = r->r.dir.uidlist; /* force a read */ c->ctl.index = SIGS_PER_RECORD; r->r.sig.next = 0; } /* need a loop to skip over deleted sigs */ do { if( c->ctl.index >= SIGS_PER_RECORD ) { /* read the record */ rnum = r->r.sig.next; if( !rnum && c->ctl.nextuid ) { /* read next uid record */ read_record( c->ctl.nextuid, r, RECTYPE_UID ); c->ctl.nextuid = r->r.uid.next; rnum = r->r.uid.siglist; } if( !rnum ) { c->ctl.eof = 1; return -1; /* return eof */ } read_record( rnum, r, RECTYPE_SIG ); if( r->r.sig.lid != c->lid ) { log_error(_("chained sigrec %lu has a wrong owner\n"), rnum ); c->ctl.eof = 1; die_invalid_db(); } c->ctl.index = 0; } } while( !r->r.sig.sig[c->ctl.index++].lid ); c->sig_lid = r->r.sig.sig[c->ctl.index-1].lid; c->sig_flag = r->r.sig.sig[c->ctl.index-1].flag; return 0; } /*********************************************** ************* Trust stuff ****************** ***********************************************/ /**************** * Verify that all our public keys are in the trustDB. */ static int verify_own_keys() { int rc; void *enum_context = NULL; PKT_secret_key *sk = m_alloc_clear( sizeof *sk ); PKT_public_key *pk = m_alloc_clear( sizeof *pk ); u32 keyid[2]; while( !(rc=enum_secret_keys( &enum_context, sk, 0 ) ) ) { keyid_from_sk( sk, keyid ); if( DBG_TRUST ) log_debug("key %08lX: checking secret key\n", (ulong)keyid[1] ); if( is_secret_key_protected( sk ) < 1 ) log_info(_("NOTE: secret key %08lX is NOT protected.\n"), (ulong)keyid[1] ); /* see whether we can access the public key of this secret key */ memset( pk, 0, sizeof *pk ); rc = get_pubkey( pk, keyid ); if( rc ) { log_info(_("key %08lX: secret key without public key - skipped\n"), (ulong)keyid[1] ); goto skip; } if( cmp_public_secret_key( pk, sk ) ) { log_info(_("key %08lX: secret and public key don't match\n"), (ulong)keyid[1] ); goto skip; } /* make sure that the pubkey is in the trustdb */ rc = query_trust_record( pk ); if( rc == -1 ) { /* put it into the trustdb */ rc = insert_trust_record( pk ); if( rc ) { log_error(_("key %08lX: can't put it into the trustdb\n"), (ulong)keyid[1] ); goto skip; } } else if( rc ) { log_error(_("key %08lX: query record failed\n"), (ulong)keyid[1] ); goto skip; } if( DBG_TRUST ) log_debug("key %08lX.%lu: stored into ultikey_table\n", (ulong)keyid[1], pk->local_id ); if( ins_lid_table_item( ultikey_table, pk->local_id, 0 ) ) log_error(_("key %08lX: already in secret key table\n"), (ulong)keyid[1]); else if( opt.verbose > 1 ) log_info(_("key %08lX: accepted as secret key.\n"), (ulong)keyid[1]); skip: release_secret_key_parts( sk ); release_public_key_parts( pk ); } if( rc != -1 ) log_error(_("enumerate secret keys failed: %s\n"), g10_errstr(rc) ); else rc = 0; enum_secret_keys( &enum_context, NULL, 0 ); /* free context */ free_secret_key( sk ); free_public_key( pk ); return rc; } static void print_user_id( const char *text, u32 *keyid ) { char *p; size_t n; p = get_user_id( keyid, &n ); if( *text ) { fputs( text, stdout); putchar(' '); } putchar('\"'); print_string( stdout, p, n, 0 ); putchar('\"'); putchar('\n'); m_free(p); } #if 0 static int print_keyid( FILE *fp, ulong lid ) { u32 ki[2]; if( keyid_from_lid( lid, ki ) ) return fprintf(fp, "????????.%lu", lid ); else return fprintf(fp, "%08lX.%lu", (ulong)ki[1], lid ); } static int print_trust( FILE *fp, unsigned trust ) { int c; switch( trust ) { case TRUST_UNKNOWN: c = 'o'; break; case TRUST_EXPIRED: c = 'e'; break; case TRUST_UNDEFINED: c = 'q'; break; case TRUST_NEVER: c = 'n'; break; case TRUST_MARGINAL: c = 'm'; break; case TRUST_FULLY: c = 'f'; break; case TRUST_ULTIMATE: c = 'u'; break; default: fprintf(fp, "%02x", trust ); return 2; } putc(c, fp); return 1; } #endif static int print_sigflags( FILE *fp, unsigned flags ) { if( flags & SIGF_CHECKED ) { fprintf(fp,"%c%c%c", (flags & SIGF_VALID) ? 'V':'-', (flags & SIGF_EXPIRED) ? 'E':'-', (flags & SIGF_REVOKED) ? 'R':'-'); } else if( flags & SIGF_NOPUBKEY) fputs("?--", fp); else fputs("---", fp); return 3; } /* (a non-recursive algorithm would be easier) */ static int do_list_sigs( ulong root, ulong pk_lid, int depth, LOCAL_ID_TABLE lids, unsigned *lineno ) { SIGREC_CONTEXT sx; int rc; u32 keyid[2]; memset( &sx, 0, sizeof sx ); sx.lid = pk_lid; for(;;) { rc = walk_sigrecs( &sx ); /* should we replace it and use */ if( rc ) /* use a loop like in collect_paths ??*/ break; rc = keyid_from_lid( sx.sig_lid, keyid ); if( rc ) { printf("%6u: %*s????????.%lu:", *lineno, depth*4, "", sx.sig_lid ); print_sigflags( stdout, sx.sig_flag ); putchar('\n'); ++*lineno; } else { printf("%6u: %*s%08lX.%lu:", *lineno, depth*4, "", (ulong)keyid[1], sx.sig_lid ); print_sigflags( stdout, sx.sig_flag ); putchar(' '); /* check whether we already checked this pk_lid */ if( !qry_lid_table_flag( ultikey_table, sx.sig_lid, NULL ) ) { print_user_id("[ultimately trusted]", keyid); ++*lineno; } else if( sx.sig_lid == pk_lid ) { printf("[self-signature]\n"); ++*lineno; } else if( sx.sig_lid == root ) { printf("[closed]\n"); ++*lineno; } else if( ins_lid_table_item( lids, sx.sig_lid, *lineno ) ) { unsigned refline; qry_lid_table_flag( lids, sx.sig_lid, &refline ); printf("[see line %u]\n", refline); ++*lineno; } else if( depth+1 >= MAX_LIST_SIGS_DEPTH ) { print_user_id( "[too deeply nested]", keyid ); ++*lineno; } else { print_user_id( "", keyid ); ++*lineno; rc = do_list_sigs( root, sx.sig_lid, depth+1, lids, lineno ); if( rc ) break; } } } return rc==-1? 0 : rc; } /**************** * List all signatures of a public key */ static int list_sigs( ulong pubkey_id ) { int rc; u32 keyid[2]; LOCAL_ID_TABLE lids; unsigned lineno = 1; rc = keyid_from_lid( pubkey_id, keyid ); if( rc ) return rc; printf("Signatures of %08lX.%lu ", (ulong)keyid[1], pubkey_id ); print_user_id("", keyid); printf("----------------------\n"); lids = new_lid_table(); rc = do_list_sigs( pubkey_id, pubkey_id, 0, lids, &lineno ); putchar('\n'); release_lid_table(lids); return rc; } /**************** * List all records of a public key */ static int list_records( ulong lid ) { int rc; TRUSTREC dr, ur, rec; ulong recno; rc = tdbio_read_record( lid, &dr, RECTYPE_DIR ); if( rc ) { log_error(_("lid %lu: read dir record failed: %s\n"), lid, g10_errstr(rc)); return rc; } tdbio_dump_record( &dr, stdout ); for( recno=dr.r.dir.keylist; recno; recno = rec.r.key.next ) { rc = tdbio_read_record( recno, &rec, 0 ); if( rc ) { log_error(_("lid %lu: read key record failed: %s\n"), lid, g10_errstr(rc)); return rc; } tdbio_dump_record( &rec, stdout ); } for( recno=dr.r.dir.uidlist; recno; recno = ur.r.uid.next ) { rc = tdbio_read_record( recno, &ur, RECTYPE_UID ); if( rc ) { log_error(_("lid %lu: read uid record failed: %s\n"), lid, g10_errstr(rc)); return rc; } tdbio_dump_record( &ur, stdout ); /* preference records */ for(recno=ur.r.uid.prefrec; recno; recno = rec.r.pref.next ) { rc = tdbio_read_record( recno, &rec, RECTYPE_PREF ); if( rc ) { log_error(_("lid %lu: read pref record failed: %s\n"), lid, g10_errstr(rc)); return rc; } tdbio_dump_record( &rec, stdout ); } /* sig records */ for(recno=ur.r.uid.siglist; recno; recno = rec.r.sig.next ) { rc = tdbio_read_record( recno, &rec, RECTYPE_SIG ); if( rc ) { log_error(_("lid %lu: read sig record failed: %s\n"), lid, g10_errstr(rc)); return rc; } tdbio_dump_record( &rec, stdout ); } } /* add cache record dump here */ return rc; } /**************** * stack is an array of (max_path+1) elements. If trust_seg_head is not * NULL it is a pointer to a variable which will receive a linked list * of trust paths - The caller has to free the memory. */ static int collect_paths( int depth, int max_depth, int all, TRUSTREC *drec, TRUST_INFO *stack, TRUST_SEG_LIST *trust_seg_head ) { ulong rn, uidrn; int marginal=0; int fully=0; LOCAL_ID_TABLE sigs_seen = NULL; if( depth >= max_depth ) /* max cert_depth reached */ return TRUST_UNDEFINED; { int i; for(i=0; i < depth; i++ ) if( stack[i].lid == drec->r.dir.lid ) return TRUST_UNDEFINED; /* closed (we already visited this lid) */ } stack[depth].lid = drec->r.dir.lid; stack[depth].otrust = drec->r.dir.ownertrust; stack[depth].trust = 0; if( !qry_lid_table_flag( ultikey_table, drec->r.dir.lid, NULL ) ) { /* we are at the end of a path */ TRUST_SEG_LIST tsl; int i; stack[depth].trust = TRUST_ULTIMATE; if( trust_seg_head ) { /* we can now put copy our current stack to the trust_seg_list */ tsl = m_alloc( sizeof *tsl + (depth+1)*sizeof( TRUST_INFO ) ); for(i=0; i <= depth; i++ ) tsl->path[i] = stack[i]; tsl->pathlen = i; tsl->next = *trust_seg_head; *trust_seg_head = tsl; } return TRUST_ULTIMATE; } /* loop over all user-ids */ if( !all ) sigs_seen = new_lid_table(); for( rn = drec->r.dir.uidlist; rn; rn = uidrn ) { TRUSTREC rec; /* used for uids and sigs */ ulong sigrn; read_record( rn, &rec, RECTYPE_UID ); uidrn = rec.r.uid.next; if( !(rec.r.uid.uidflags & UIDF_CHECKED) ) continue; /* user id has not been checked */ if( !(rec.r.uid.uidflags & UIDF_VALID) ) continue; /* user id is not valid */ if( (rec.r.uid.uidflags & UIDF_REVOKED) ) continue; /* user id has been revoked */ /* loop over all signature records */ for( rn = rec.r.uid.siglist; rn; rn = sigrn ) { int i; read_record( rn, &rec, RECTYPE_SIG ); sigrn = rec.r.sig.next; for(i=0; i < SIGS_PER_RECORD; i++ ) { TRUSTREC tmp; int ot, nt; if( !rec.r.sig.sig[i].lid ) continue; /* skip deleted sigs */ if( !(rec.r.sig.sig[i].flag & SIGF_CHECKED) ) continue; /* skip unchecked signatures */ if( !(rec.r.sig.sig[i].flag & SIGF_VALID) ) continue; /* skip invalid signatures */ if( (rec.r.sig.sig[i].flag & SIGF_EXPIRED) ) continue; /* skip expired signatures */ if( (rec.r.sig.sig[i].flag & SIGF_REVOKED) ) continue; /* skip revoked signatures */ /* visit every signer only once (a signer may have * signed more than one user ID) */ if( sigs_seen && ins_lid_table_item( sigs_seen, rec.r.sig.sig[i].lid, 0) ) continue; /* we already have this one */ read_record( rec.r.sig.sig[i].lid, &tmp, 0 ); if( tmp.rectype != RECTYPE_DIR ) { if( tmp.rectype != RECTYPE_SDIR ) log_info("oops: lid %lu: sig %lu has rectype %d" " - skipped\n", drec->r.dir.lid, tmp.recnum, tmp.rectype ); continue; } ot = tmp.r.dir.ownertrust & TRUST_MASK; if( ot >= TRUST_FULLY ) ot = TRUST_FULLY; /* just in case */ nt = collect_paths( depth+1, max_depth, all, &tmp, stack, trust_seg_head ); nt &= TRUST_MASK; if( nt < TRUST_MARGINAL ) { continue; } if( nt == TRUST_ULTIMATE ) { /* we have signed this key and only in this special case * we assume that this one is fully trusted */ if( !all ) { if( sigs_seen ) release_lid_table( sigs_seen ); return (stack[depth].trust = TRUST_FULLY); } } if( nt >= TRUST_FULLY ) fully++; if( nt >= TRUST_MARGINAL ) marginal++; if( fully >= opt.completes_needed || marginal >= opt.marginals_needed ) { if( !all ) { if( sigs_seen ) release_lid_table( sigs_seen ); return (stack[depth].trust = TRUST_FULLY); } } } } } if( sigs_seen ) release_lid_table( sigs_seen ); if( all && ( fully >= opt.completes_needed || marginal >= opt.marginals_needed ) ) { return (stack[depth].trust = TRUST_FULLY ); } if( marginal ) { return (stack[depth].trust = TRUST_MARGINAL); } return (stack[depth].trust=TRUST_UNDEFINED); } /**************** * Given the directory record of a key, check whether we can * find a path to an ultimately trusted key. We do this by * checking all key signatures up to a some depth. */ static int verify_key( int max_depth, TRUSTREC *drec ) { TRUST_INFO *tmppath = m_alloc_clear( (max_depth+1)* sizeof *tmppath ); int tr; tr = collect_paths( 0, max_depth, 0, drec, tmppath, NULL ); m_free( tmppath ); return tr; } /**************** * we have the pubkey record and all needed informations are in the trustdb * but nothing more is known. */ static int do_check( TRUSTREC *dr, unsigned *validity ) { if( !dr->r.dir.keylist ) { log_error(_("Ooops, no keys\n")); return G10ERR_TRUSTDB; } if( !dr->r.dir.uidlist ) { log_error(_("Ooops, no user ids\n")); return G10ERR_TRUSTDB; } if( tdbio_db_matches_options() && (dr->r.dir.dirflags & DIRF_VALVALID) && dr->r.dir.validity ) *validity = dr->r.dir.validity; else { *validity = verify_key( opt.max_cert_depth, dr ); if( (*validity & TRUST_MASK) >= TRUST_UNDEFINED && tdbio_db_matches_options() ) { /* update the cached validity value */ dr->r.dir.validity = (*validity & TRUST_MASK); dr->r.dir.dirflags |= DIRF_VALVALID; write_record( dr ); } } if( dr->r.dir.dirflags & DIRF_REVOKED ) *validity |= TRUST_FLAG_REVOKED; return 0; } /**************** * Perform some checks over the trustdb * level 0: only open the db * 1: used for initial program startup */ int init_trustdb( int level, const char *dbname ) { int rc=0; if( !ultikey_table ) ultikey_table = new_lid_table(); if( !level || level==1 ) { rc = tdbio_set_dbname( dbname, !!level ); if( rc ) return rc; if( !level ) return 0; /* verify that our own keys are in the trustDB * or move them to the trustdb. */ rc = verify_own_keys(); /* should we check whether there is no other ultimately trusted * key in the database? */ } else BUG(); return rc; } void list_trustdb( const char *username ) { TRUSTREC rec; if( username && *username == '#' ) { int rc; ulong lid = atoi(username+1); if( (rc = list_records( lid)) ) log_error(_("user '%s' read problem: %s\n"), username, g10_errstr(rc)); else if( (rc = list_sigs( lid )) ) log_error(_("user '%s' list problem: %s\n"), username, g10_errstr(rc)); } else if( username ) { PKT_public_key *pk = m_alloc_clear( sizeof *pk ); int rc; if( (rc = get_pubkey_byname( NULL, pk, username, NULL )) ) log_error(_("user '%s' not found: %s\n"), username, g10_errstr(rc) ); else if( (rc=tdbio_search_dir_bypk( pk, &rec )) && rc != -1 ) log_error(_("problem finding '%s' in trustdb: %s\n"), username, g10_errstr(rc)); else if( rc == -1 ) log_error(_("user '%s' not in trustdb\n"), username); else if( (rc = list_records( pk->local_id)) ) log_error(_("user '%s' read problem: %s\n"), username, g10_errstr(rc)); else if( (rc = list_sigs( pk->local_id )) ) log_error(_("user '%s' list problem: %s\n"), username, g10_errstr(rc)); free_public_key( pk ); } else { ulong recnum; int i; printf("TrustDB: %s\n", tdbio_get_dbname() ); for(i=9+strlen(tdbio_get_dbname()); i > 0; i-- ) putchar('-'); putchar('\n'); for(recnum=0; !tdbio_read_record( recnum, &rec, 0); recnum++ ) tdbio_dump_record( &rec, stdout ); } } /**************** * Print a list of all defined owner trust value. */ void export_ownertrust() { TRUSTREC rec; TRUSTREC rec2; ulong recnum; int i; byte *p; int rc; printf(_("# List of assigned trustvalues, created %s\n" "# (Use \"gpgm --import-ownertrust\" to restore them)\n"), asctimestamp( make_timestamp() ) ); for(recnum=0; !tdbio_read_record( recnum, &rec, 0); recnum++ ) { if( rec.rectype == RECTYPE_DIR ) { if( !rec.r.dir.keylist ) { log_error(_("directory record w/o primary key\n")); continue; } if( !rec.r.dir.ownertrust ) continue; rc = tdbio_read_record( rec.r.dir.keylist, &rec2, RECTYPE_KEY); if( rc ) { log_error(_("error reading key record: %s\n"), g10_errstr(rc)); continue; } p = rec2.r.key.fingerprint; for(i=0; i < rec2.r.key.fingerprint_len; i++, p++ ) printf("%02X", *p ); printf(":%u:\n", (unsigned)rec.r.dir.ownertrust ); } } } void import_ownertrust( const char *fname ) { FILE *fp; int is_stdin=0; char line[256]; char *p; size_t n, fprlen; unsigned otrust; if( !fname || (*fname == '-' && !fname[1]) ) { fp = stdin; fname = "[stdin]"; is_stdin = 1; } else if( !(fp = fopen( fname, "r" )) ) { log_error_f(fname, _("can't open file: %s\n"), strerror(errno) ); return; } while( fgets( line, DIM(line)-1, fp ) ) { TRUSTREC rec; int rc; if( !*line || *line == '#' ) continue; n = strlen(line); if( line[n-1] != '\n' ) { log_error_f(fname, _("line to long\n") ); /* ... or last line does not have a LF */ break; /* can't continue */ } for(p = line; *p && *p != ':' ; p++ ) if( !isxdigit(*p) ) break; if( *p != ':' ) { log_error_f(fname, _("error: missing colon\n") ); continue; } fprlen = p - line; if( fprlen != 32 && fprlen != 40 ) { log_error_f(fname, _("error: invalid fingerprint\n") ); continue; } if( sscanf(p, ":%u:", &otrust ) != 1 ) { log_error_f(fname, _("error: no ownertrust value\n") ); continue; } if( !otrust ) continue; /* no otrust defined - no need to update or insert */ /* convert the ascii fingerprint to binary */ for(p=line, fprlen=0; *p != ':'; p += 2 ) line[fprlen++] = HEXTOBIN(p[0]) * 16 + HEXTOBIN(p[1]); line[fprlen] = 0; repeat: rc = tdbio_search_dir_byfpr( line, fprlen, 0, &rec ); if( !rc ) { /* found: update */ if( rec.r.dir.ownertrust ) log_info(_("LID %lu: changing trust from %u to %u\n"), rec.r.dir.lid, rec.r.dir.ownertrust, otrust ); else log_info(_("LID %lu: setting trust to %u\n"), rec.r.dir.lid, otrust ); rec.r.dir.ownertrust = otrust; write_record( &rec ); } else if( rc == -1 ) { /* not found; get the key from the ring */ PKT_public_key *pk = m_alloc_clear( sizeof *pk ); log_info_f(fname, _("key not in trustdb, searching ring.\n")); rc = get_pubkey_byfprint( pk, line, fprlen ); if( rc ) log_info_f(fname, _("key not in ring: %s\n"), g10_errstr(rc)); else { rc = query_trust_record( pk ); /* only as assertion */ if( rc != -1 ) log_error_f(fname, _("Oops: key is now in trustdb???\n")); else { rc = insert_trust_record( pk ); if( !rc ) goto repeat; /* update the ownertrust */ log_error_f(fname, _("insert trust record failed: %s\n"), g10_errstr(rc) ); } } } else /* error */ log_error_f(fname, _("error finding dir record: %s\n"), g10_errstr(rc)); } if( ferror(fp) ) log_error_f(fname, _("read error: %s\n"), strerror(errno) ); if( !is_stdin ) fclose(fp); do_sync(); } static void print_path( int pathlen, TRUST_INFO *path ) { int rc, i; u32 keyid[2]; fputs("path:", stdout); for( i = 0; i < pathlen; i++ ) { if( i && !(i%4) ) fputs(" ", stdout ); rc = keyid_from_lid( path[i].lid, keyid ); if( rc ) printf(" ????????.%lu:", path[i].lid ); else printf(" %08lX.%lu:", (ulong)keyid[1], path[i].lid ); print_sigflags( stdout, path[i].otrust ); } putchar('\n'); } void list_trust_path( int max_depth, const char *username ) { int rc; int wipe=0; TRUSTREC rec; TRUST_INFO *tmppath; TRUST_SEG_LIST trust_seg_list, tsl, tsl2; PKT_public_key *pk = m_alloc_clear( sizeof *pk ); if( max_depth < 0 ) { wipe = 1; max_depth = -max_depth; } if( (rc = get_pubkey_byname(NULL, pk, username, NULL )) ) log_error(_("user '%s' not found: %s\n"), username, g10_errstr(rc) ); else if( (rc=tdbio_search_dir_bypk( pk, &rec )) && rc != -1 ) log_error(_("problem finding '%s' in trustdb: %s\n"), username, g10_errstr(rc)); else if( rc == -1 ) { log_info(_("user '%s' not in trustdb - inserting\n"), username); rc = insert_trust_record( pk ); if( rc ) log_error(_("failed to put '%s' into trustdb: %s\n"), username, g10_errstr(rc)); else { assert( pk->local_id ); } } free_public_key( pk ); /* collect the paths */ tmppath = m_alloc_clear( (max_depth+1)* sizeof *tmppath ); trust_seg_list = NULL; collect_paths( 0, max_depth, 1, &rec, tmppath, &trust_seg_list ); m_free( tmppath ); /* and now print them */ for(tsl = trust_seg_list; tsl; tsl = tsl->next ) { print_path( tsl->pathlen, tsl->path ); } /* release the list */ for(tsl = trust_seg_list; tsl; tsl = tsl2 ) { tsl2 = tsl->next; m_free( tsl ); } trust_seg_list = NULL; } /**************** * Check the complete trustdb or only the entries for the given username. * We check the complete database and recalculate all flags. */ void check_trustdb( const char *username ) { TRUSTREC rec; KBNODE keyblock = NULL; KBPOS kbpos; int rc; if( username ) { rc = find_keyblock_byname( &kbpos, username ); if( !rc ) rc = read_keyblock( &kbpos, &keyblock ); if( rc ) { log_error(_("%s: keyblock read problem: %s\n"), username, g10_errstr(rc)); } else { int modified; rc = update_trust_record( keyblock, 0, &modified ); if( rc == -1 ) { /* not yet in trustdb: insert */ rc = insert_trust_record( find_kbnode( keyblock, PKT_PUBLIC_KEY ) ->pkt->pkt.public_key ); } if( rc ) log_error(_("%s: update failed: %s\n"), username, g10_errstr(rc) ); else if( modified ) log_info(_("%s: updated\n"), username ); else log_info(_("%s: okay\n"), username ); } release_kbnode( keyblock ); keyblock = NULL; } else { ulong recnum; ulong count=0, upd_count=0, err_count=0, skip_count=0; for(recnum=0; !tdbio_read_record( recnum, &rec, 0); recnum++ ) { if( rec.rectype == RECTYPE_DIR ) { TRUSTREC tmp; int modified; if( !rec.r.dir.keylist ) { log_info(_("lid %lu: dir record w/o key - skipped\n"), recnum); count++; skip_count++; continue; } read_record( rec.r.dir.keylist, &tmp, RECTYPE_KEY ); rc = get_keyblock_byfprint( &keyblock, tmp.r.key.fingerprint, tmp.r.key.fingerprint_len ); if( rc ) { log_error(_("lid %lu: keyblock not found: %s\n"), recnum, g10_errstr(rc) ); count++; skip_count++; continue; } rc = update_trust_record( keyblock, 0, &modified ); if( rc ) { log_error(_("lid %lu: update failed: %s\n"), recnum, g10_errstr(rc) ); err_count++; } else if( modified ) { if( opt.verbose ) log_info(_("lid %lu: updated\n"), recnum ); upd_count++; } else if( opt.verbose > 1 ) log_info(_("lid %lu: okay\n"), recnum ); release_kbnode( keyblock ); keyblock = NULL; if( !(++count % 100) ) log_info(_("%lu keys so far processed\n"), count); } } log_info(_("%lu keys processed\n"), count); if( skip_count ) log_info(_("\t%lu keys skipped\n"), skip_count); if( err_count ) log_info(_("\t%lu keys with errors\n"), err_count); if( upd_count ) log_info(_("\t%lu keys updated\n"), upd_count); } } /**************** * Put new entries from the pubrings into the trustdb. * This function honors the sig flags to speed up the check. */ void update_trustdb( ) { KBNODE keyblock = NULL; KBPOS kbpos; int rc; rc = enum_keyblocks( 0, &kbpos, &keyblock ); if( !rc ) { ulong count=0, upd_count=0, err_count=0, new_count=0; while( !(rc = enum_keyblocks( 1, &kbpos, &keyblock )) ) { int modified; rc = update_trust_record( keyblock, 1, &modified ); if( rc == -1 ) { /* not yet in trustdb: insert */ PKT_public_key *pk = find_kbnode( keyblock, PKT_PUBLIC_KEY ) ->pkt->pkt.public_key; rc = insert_trust_record( pk ); if( rc && !pk->local_id ) { log_error(_("lid ?: insert failed: %s\n"), g10_errstr(rc) ); err_count++; } else if( rc ) { log_error(_("lid %lu: insert failed: %s\n"), pk->local_id, g10_errstr(rc) ); err_count++; } else { if( opt.verbose ) log_info(_("lid %lu: inserted\n"), pk->local_id ); new_count++; } } else if( rc ) { log_error(_("lid %lu: update failed: %s\n"), lid_from_keyblock(keyblock), g10_errstr(rc) ); err_count++; } else if( modified ) { if( opt.verbose ) log_info(_("lid %lu: updated\n"), lid_from_keyblock(keyblock)); upd_count++; } else if( opt.verbose > 1 ) log_info(_("lid %lu: okay\n"), lid_from_keyblock(keyblock) ); release_kbnode( keyblock ); keyblock = NULL; if( !(++count % 100) ) log_info(_("%lu keys so far processed\n"), count); } log_info(_("%lu keys processed\n"), count); if( err_count ) log_info(_("\t%lu keys with errors\n"), err_count); if( upd_count ) log_info(_("\t%lu keys updated\n"), upd_count); if( new_count ) log_info(_("\t%lu keys inserted\n"), new_count); } if( rc && rc != -1 ) log_error(_("enumerate keyblocks failed: %s\n"), g10_errstr(rc)); enum_keyblocks( 2, &kbpos, &keyblock ); /* close */ release_kbnode( keyblock ); } /**************** * Get the trustlevel for this PK. * Note: This does not ask any questions * Returns: 0 okay of an errorcode * * It operates this way: * locate the pk 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: * try to insert the pubkey into the trustdb and check 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_trust( PKT_public_key *pk, unsigned *r_trustlevel ) { TRUSTREC rec; unsigned trustlevel = TRUST_UNKNOWN; int rc=0; u32 cur_time; u32 keyid[2]; keyid_from_pk( pk, keyid ); /* get the pubkey record */ if( pk->local_id ) { read_record( pk->local_id, &rec, RECTYPE_DIR ); } else { /* no local_id: scan the trustdb */ if( (rc=tdbio_search_dir_bypk( pk, &rec )) && rc != -1 ) { log_error(_("check_trust: search dir record failed: %s\n"), g10_errstr(rc)); return rc; } else if( rc == -1 ) { /* not found - insert */ rc = insert_trust_record( pk ); if( rc ) { log_error(_("key %08lX: insert trust record failed: %s\n"), (ulong)keyid[1], g10_errstr(rc)); goto leave; } log_info(_("key %08lX.%lu: inserted into trustdb\n"), (ulong)keyid[1], pk->local_id ); /* and re-read the dir record */ read_record( pk->local_id, &rec, RECTYPE_DIR ); } } cur_time = make_timestamp(); if( pk->timestamp > cur_time ) { log_info(_("key %08lX.%lu: created in future " "(time warp or clock problem)\n"), (ulong)keyid[1], pk->local_id ); return G10ERR_TIME_CONFLICT; } if( pk->expiredate && pk->expiredate <= cur_time ) { log_info(_("key %08lX.%lu: expired at %s\n"), (ulong)keyid[1], pk->local_id, asctimestamp( pk->expiredate) ); trustlevel = TRUST_EXPIRED; } else { rc = do_check( &rec, &trustlevel ); if( rc ) { log_error(_("key %08lX.%lu: trust check failed: %s\n"), (ulong)keyid[1], pk->local_id, g10_errstr(rc)); return rc; } } leave: if( DBG_TRUST ) log_debug("check_trust() returns trustlevel %04x.\n", trustlevel); *r_trustlevel = trustlevel; return 0; } int query_trust_info( PKT_public_key *pk ) { unsigned trustlevel; int c; if( check_trust( pk, &trustlevel ) ) return '?'; if( trustlevel & TRUST_FLAG_REVOKED ) return 'r'; switch( (trustlevel & TRUST_MASK) ) { case TRUST_UNKNOWN: c = 'o'; break; case TRUST_EXPIRED: c = 'e'; break; case TRUST_UNDEFINED: c = 'q'; break; case TRUST_NEVER: c = 'n'; break; case TRUST_MARGINAL: c = 'm'; break; case TRUST_FULLY: c = 'f'; break; case TRUST_ULTIMATE: c = 'u'; break; default: BUG(); } return c; } /**************** * Enumerate all keys, which are needed to build all trust paths for * the given key. This function does not return the key itself or * the ultimate key (the last point in cerificate chain). Only * certificate chains which ends up at an ultimately trusted key * are listed. If ownertrust or validity is not NULL, the corresponding * value for the returned LID is also returned in these variable(s). * * 1) create a void pointer and initialize it to NULL * 2) pass this void pointer by reference to this function. * Set lid to the key you want to enumerate and pass it by reference. * 3) call this function as long as it does not return -1 * to indicate EOF. LID does contain the next key used to build the web * 4) Always call this function a last time with LID set to NULL, * so that it can free its context. * * Returns: -1 on EOF or the level of the returned LID */ int enum_cert_paths( void **context, ulong *lid, unsigned *ownertrust, unsigned *validity ) { struct { int init; TRUST_SEG_LIST tsl_head; TRUST_SEG_LIST tsl; int idx; } *ctx; TRUST_SEG_LIST tsl; if( !lid ) { /* release the context */ if( *context ) { TRUST_SEG_LIST tsl2; ctx = *context; for(tsl = ctx->tsl_head; tsl; tsl = tsl2 ) { tsl2 = tsl->next; m_free( tsl ); } *context = NULL; } return -1; } if( !*context ) { TRUST_INFO *tmppath; TRUSTREC rec; if( !lid ) return -1; ctx = m_alloc_clear( sizeof *ctx ); *context = ctx; /* collect the paths */ read_record( *lid, &rec, RECTYPE_DIR ); tmppath = m_alloc_clear( (opt.max_cert_depth+1)* sizeof *tmppath ); tsl = NULL; collect_paths( 0, opt.max_cert_depth, 1, &rec, tmppath, &tsl ); m_free( tmppath ); /* and now print them */ ctx->tsl_head = tsl; ctx->tsl = ctx->tsl_head; ctx->idx = 0; } else ctx = *context; while( ctx->tsl && ctx->idx >= tsl->pathlen ) { ctx->tsl = ctx->tsl->next; ctx->idx = 0; } tsl = ctx->tsl; if( !tsl ) return -1; /* eof */ if( ownertrust ) *ownertrust = tsl->path[ctx->idx].otrust; if( validity ) *validity = tsl->path[ctx->idx].trust; *lid = tsl->path[ctx->idx].lid; ctx->idx++; return ctx->idx-1; } /**************** * Return the assigned ownertrust value for the given LID */ unsigned get_ownertrust( ulong lid ) { TRUSTREC rec; read_record( lid, &rec, RECTYPE_DIR ); return rec.r.dir.ownertrust; } int get_ownertrust_info( ulong lid ) { unsigned otrust; int c; otrust = get_ownertrust( lid ); switch( (otrust & TRUST_MASK) ) { case TRUST_NEVER: c = 'n'; break; case TRUST_MARGINAL: c = 'm'; break; case TRUST_FULLY: c = 'f'; break; case TRUST_ULTIMATE: c = 'u'; break; default: c = '-'; break; } return c; } byte * get_pref_data( ulong lid, const byte *namehash, size_t *ret_n ) { TRUSTREC rec; ulong recno; read_record( lid, &rec, RECTYPE_DIR ); for( recno=rec.r.dir.uidlist; recno; recno = rec.r.uid.next ) { read_record( recno, &rec, RECTYPE_UID ); if( rec.r.uid.prefrec && ( !namehash || !memcmp(namehash, rec.r.uid.namehash, 20) )) { byte *buf; /* found the correct one or the first one */ read_record( rec.r.uid.prefrec, &rec, RECTYPE_PREF ); if( rec.r.pref.next ) log_info(_("WARNING: can't yet handle long pref records\n")); buf = m_alloc( ITEMS_PER_PREF_RECORD ); memcpy( buf, rec.r.pref.data, ITEMS_PER_PREF_RECORD ); *ret_n = ITEMS_PER_PREF_RECORD; return buf; } } return NULL; } /**************** * Check whether the algorithm is in one of the pref records */ int is_algo_in_prefs( ulong lid, int preftype, int algo ) { TRUSTREC rec; ulong recno; int i; byte *pref; read_record( lid, &rec, RECTYPE_DIR ); for( recno=rec.r.dir.uidlist; recno; recno = rec.r.uid.next ) { read_record( recno, &rec, RECTYPE_UID ); if( rec.r.uid.prefrec ) { read_record( rec.r.uid.prefrec, &rec, RECTYPE_PREF ); if( rec.r.pref.next ) log_info(_("WARNING: can't yet handle long pref records\n")); pref = rec.r.pref.data; for(i=0; i+1 < ITEMS_PER_PREF_RECORD; i+=2 ) { if( pref[i] == preftype && pref[i+1] == algo ) return 1; } } } return 0; } static int get_dir_record( PKT_public_key *pk, TRUSTREC *rec ) { int rc=0; if( pk->local_id ) { read_record( pk->local_id, rec, RECTYPE_DIR ); } else { /* no local_id: scan the trustdb */ if( (rc=tdbio_search_dir_bypk( pk, rec )) && rc != -1 ) log_error(_("get_dir_record: search_record failed: %s\n"), g10_errstr(rc)); } return rc; } /**************** * This function simply looks for the key in the trustdb * and makes sure that pk->local_id is set to the coreect value. * Return: 0 = found * -1 = not found * other = error */ int query_trust_record( PKT_public_key *pk ) { TRUSTREC rec; return get_dir_record( pk, &rec ); } int clear_trust_checked_flag( PKT_public_key *pk ) { TRUSTREC rec; int rc; rc = get_dir_record( pk, &rec ); if( rc ) return rc; /* check whether they are already reset */ if( !(rec.r.dir.dirflags & DIRF_CHECKED) && !(rec.r.dir.dirflags & DIRF_VALVALID) ) return 0; /* reset the flag */ rec.r.dir.dirflags &= ~DIRF_CHECKED; rec.r.dir.dirflags &= ~DIRF_VALVALID; write_record( &rec ); do_sync(); return 0; } static void check_hint_sig( ulong lid, KBNODE keyblock, u32 *keyid, byte *uidrec_hash, TRUSTREC *sigrec, int sigidx, ulong hint_owner ) { KBNODE node; int rc, state; byte uhash[20]; int is_selfsig; PKT_signature *sigpkt = NULL; TRUSTREC tmp; u32 sigkid[2]; int revoke = 0; if( sigrec->r.sig.sig[sigidx].flag & SIGF_CHECKED ) log_info(_("NOTE: sig rec %lu[%d] in hintlist " "of %lu but marked as checked\n"), sigrec->recnum, sigidx, hint_owner ); if( !(sigrec->r.sig.sig[sigidx].flag & SIGF_NOPUBKEY) ) log_info(_("NOTE: sig rec %lu[%d] in hintlist " "of %lu but not marked\n"), sigrec->recnum, sigidx, hint_owner ); read_record( sigrec->r.sig.sig[sigidx].lid, &tmp, 0 ); if( tmp.rectype != RECTYPE_DIR ) { /* we need the dir record */ log_error(_("sig rec %lu[%d] in hintlist " "of %lu does not point to a dir record\n"), sigrec->recnum, sigidx, hint_owner ); return; } if( !tmp.r.dir.keylist ) { log_error(_("lid %lu: no primary key\n"), tmp.r.dir.lid ); return; } read_record(tmp.r.dir.keylist, &tmp, RECTYPE_KEY ); keyid_from_fingerprint( tmp.r.key.fingerprint, tmp.r.key.fingerprint_len, sigkid ); /* find the correct signature packet */ state = 0; for( node=keyblock; node; node = node->next ) { if( node->pkt->pkttype == PKT_USER_ID ) { PKT_user_id *uidpkt = node->pkt->pkt.user_id; if( state ) break; rmd160_hash_buffer( uhash, uidpkt->name, uidpkt->len ); if( !memcmp( uhash, uidrec_hash, 20 ) ) state = 1; } else if( state && node->pkt->pkttype == PKT_SIGNATURE ) { sigpkt = node->pkt->pkt.signature; if( sigpkt->keyid[0] == sigkid[0] && sigpkt->keyid[1] == sigkid[1] && ( (sigpkt->sig_class&~3) == 0x10 || ( revoke = (sigpkt->sig_class == 0x30)) ) ) { state = 2; break; /* found */ } } } if( !node ) { log_info(_("lid %lu: user id not found in keyblock\n"), lid ); return ; } if( state != 2 ) { log_info(_("lid %lu: user id without signature\n"), lid ); return ; } /* and check the sig */ rc = check_key_signature( keyblock, node, &is_selfsig ); if( is_selfsig ) { log_error(_("lid %lu: self-signature in hintlist\n"), lid ); return; } /* FiXME: handling fo SIGF_REVOKED is not correct! */ if( !rc ) { /* valid signature */ if( opt.verbose ) log_info("sig %08lX.%lu/%02X%02X/%08lX: %s\n", (ulong)keyid[1], lid, uhash[18], uhash[19], (ulong)sigpkt->keyid[1], revoke? _("Valid certificate revocation") : _("Good certificate") ); sigrec->r.sig.sig[sigidx].flag = SIGF_CHECKED | SIGF_VALID; if( revoke ) sigrec->r.sig.sig[sigidx].flag |= SIGF_REVOKED; } else if( rc == G10ERR_NO_PUBKEY ) { log_info("sig %08lX.%lu/%02X%02X/%08lX: %s\n", (ulong)keyid[1], lid, uhash[18], uhash[19], (ulong)sigpkt->keyid[1], _("very strange: no public key\n") ); sigrec->r.sig.sig[sigidx].flag = SIGF_NOPUBKEY; } else { log_info("sig %08lX.%lu/%02X%02X/%08lX: %s\n", (ulong)keyid[1], lid, uhash[18], uhash[19], (ulong)sigpkt->keyid[1], g10_errstr(rc) ); sigrec->r.sig.sig[sigidx].flag = SIGF_CHECKED; } sigrec->dirty = 1; } /**************** * Process a hintlist. * Fixme: this list is not anymore anchored to another * record, so it should be put elsewehere in case of an error */ static void process_hintlist( ulong hintlist, ulong hint_owner ) { ulong hlst_rn; int rc; for( hlst_rn = hintlist; hlst_rn; ) { TRUSTREC hlstrec; int hlst_idx; read_record( hlst_rn, &hlstrec, RECTYPE_HLST ); for( hlst_idx=0; hlst_idx < ITEMS_PER_HLST_RECORD; hlst_idx++ ) { TRUSTREC dirrec; TRUSTREC uidrec; TRUSTREC tmprec; KBNODE keyblock = NULL; u32 keyid[2]; ulong lid; ulong r1, r2; lid = hlstrec.r.hlst.rnum[hlst_idx]; if( !lid ) continue; read_record( lid, &dirrec, 0 ); /* make sure it points to a dir record: * this should be true because it only makes sense to * call this function if the dir record is available */ if( dirrec.rectype != RECTYPE_DIR ) { log_error(_("hintlist %lu[%d] of %lu " "does not point to a dir record\n"), hlst_rn, hlst_idx, hint_owner ); continue; } if( !dirrec.r.dir.keylist ) { log_error(_("lid %lu does not have a key\n"), lid ); continue; } /* get the keyblock */ read_record( dirrec.r.dir.keylist, &tmprec, RECTYPE_KEY ); rc = get_keyblock_byfprint( &keyblock, tmprec.r.key.fingerprint, tmprec.r.key.fingerprint_len ); if( rc ) { log_error(_("lid %lu: can't get keyblock: %s\n"), lid, g10_errstr(rc) ); continue; } keyid_from_fingerprint( tmprec.r.key.fingerprint, tmprec.r.key.fingerprint_len, keyid ); /* Walk over all user ids and their signatures and check all * the signature which are created by hint_owner */ for( r1 = dirrec.r.dir.uidlist; r1; r1 = uidrec.r.uid.next ) { TRUSTREC sigrec; read_record( r1, &uidrec, RECTYPE_UID ); for( r2 = uidrec.r.uid.siglist; r2; r2 = sigrec.r.sig.next ) { int i; read_record( r2, &sigrec, RECTYPE_SIG ); sigrec.dirty = 0; for(i=0; i < SIGS_PER_RECORD; i++ ) { if( !sigrec.r.sig.sig[i].lid ) continue; /* skip deleted sigs */ if( sigrec.r.sig.sig[i].lid != hint_owner ) continue; /* not for us */ /* some diagnostic messages */ /* and do the signature check */ check_hint_sig( lid, keyblock, keyid, uidrec.r.uid.namehash, &sigrec, i, hint_owner ); } if( sigrec.dirty ) write_record( &sigrec ); } } release_kbnode( keyblock ); } /* loop over hlst entries */ /* delete this hlst record */ hlst_rn = hlstrec.r.hlst.next; delete_record( hlstrec.recnum ); } /* loop over hintlist */ } /**************** * Create or update shadow dir record and return the LID of the record */ static ulong create_shadow_dir( PKT_signature *sig, ulong lid ) { TRUSTREC sdir, hlst, tmphlst; ulong recno, newlid; int tmpidx=0; /* avoids gcc warnign - this is controlled by tmphlst */ int rc; /* first see whether we already have such a record */ rc = tdbio_search_sdir( sig->keyid, sig->pubkey_algo, &sdir ); if( rc && rc != -1 ) { log_error(_("tdbio_search_dir failed: %s\n"), g10_errstr(rc)); die_invalid_db(); } if( rc == -1 ) { /* not found: create */ memset( &sdir, 0, sizeof sdir ); sdir.recnum = tdbio_new_recnum(); sdir.rectype= RECTYPE_SDIR; sdir.r.sdir.lid = sdir.recnum; sdir.r.sdir.keyid[0] = sig->keyid[0]; sdir.r.sdir.keyid[1] = sig->keyid[1]; sdir.r.sdir.pubkey_algo = sig->pubkey_algo; sdir.r.sdir.hintlist = 0; write_record( &sdir ); } newlid = sdir.recnum; /* Put the record number into the hintlist. * (It is easier to use the lid and not the record number of the * key to save some space (assuming that a signator has * signed more than one user id - and it is easier to implement.) */ tmphlst.recnum = 0; for( recno=sdir.r.sdir.hintlist; recno; recno = hlst.r.hlst.next) { int i; read_record( recno, &hlst, RECTYPE_HLST ); for( i=0; i < ITEMS_PER_HLST_RECORD; i++ ) { if( !hlst.r.hlst.rnum[i] ) { if( !tmphlst.recnum ) { tmphlst = hlst; tmpidx = i; } } else if( hlst.r.hlst.rnum[i] == lid ) return newlid; /* the signature is already in the hintlist */ } } /* not yet in the hint list, write it */ if( tmphlst.recnum ) { /* we have an empty slot */ tmphlst.r.hlst.rnum[tmpidx] = lid; write_record( &tmphlst ); } else { /* must append a new hlst record */ memset( &hlst, 0, sizeof hlst ); hlst.recnum = tdbio_new_recnum(); hlst.rectype = RECTYPE_HLST; hlst.r.hlst.next = sdir.r.sdir.hintlist; hlst.r.hlst.rnum[0] = lid; write_record( &hlst ); sdir.r.sdir.hintlist = hlst.recnum; write_record( &sdir ); } return newlid; } /**************** * This function checks the given public key and inserts or updates * the keyrecord from the trustdb. Revocation certificates * are handled here and the keybinding of subkeys is checked. * Hmmm: Should we check here, that the key has at least one valid * user ID or do we allow keys w/o user ID? * * keyblock points to the first node in the keyblock, * keynode is the node with the public key to check * (either primary or secondary), keyid is the keyid of * the primary key, drec is the directory record and recno_list * is a list used to keep track of visited records. * Existing keyflags are recalculated if recheck is true. */ static void upd_key_record( KBNODE keyblock, KBNODE keynode, u32 *keyid, TRUSTREC *drec, RECNO_LIST *recno_list, int recheck ) { TRUSTREC krec; KBNODE node; PKT_public_key *pk = keynode->pkt->pkt.public_key; ulong lid = drec->recnum; byte fpr[MAX_FINGERPRINT_LEN]; size_t fprlen; ulong recno, newrecno; int keybind_seen = 0; int revoke_seen = 0; int rc; fingerprint_from_pk( pk, fpr, &fprlen ); /* do we already have this key? */ for( recno=drec->r.dir.keylist; recno; recno = krec.r.key.next ) { read_record( recno, &krec, RECTYPE_KEY ); if( krec.r.key.fingerprint_len == fprlen && !memcmp( krec.r.key.fingerprint, fpr, fprlen ) ) break; } if( recno ) { /* yes */ ins_recno_list( recno_list, recno, RECTYPE_KEY ); } else { /* no: insert this new key */ recheck = 1; memset( &krec, 0, sizeof(krec) ); krec.rectype = RECTYPE_KEY; krec.r.key.lid = lid; krec.r.key.pubkey_algo = pk->pubkey_algo; krec.r.key.fingerprint_len = fprlen; memcpy(krec.r.key.fingerprint, fpr, fprlen ); krec.recnum = newrecno = tdbio_new_recnum(); write_record( &krec ); ins_recno_list( recno_list, newrecno, RECTYPE_KEY ); /* and put this new record at the end of the keylist */ if( !(recno=drec->r.dir.keylist) ) { /* this is the first key */ drec->r.dir.keylist = newrecno; drec->dirty = 1; } else { /* we already have a key, append the new one */ TRUSTREC save = krec; for( ; recno; recno = krec.r.key.next ) read_record( recno, &krec, RECTYPE_KEY ); krec.r.key.next = newrecno; write_record( &krec ); krec = save; } } if( !recheck && (krec.r.key.keyflags & KEYF_CHECKED) ) return; /* check keybindings and revocations */ krec.r.key.keyflags = 0; if( keynode->pkt->pkttype == PKT_PUBLIC_KEY ) { /* we assume that a primary key is always valid * and check later whether we have a revocation */ krec.r.key.keyflags |= KEYF_CHECKED | KEYF_VALID; } for( node=keynode->next; node; node = node->next ) { PKT_signature *sig; if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) break; /* ready */ else if( node->pkt->pkttype != PKT_SIGNATURE ) continue; sig = node->pkt->pkt.signature; if( keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1] ) continue; /* not a self signature */ if( sig->sig_class == 0x18 && !keybind_seen ) { /* a keybinding */ if( keynode->pkt->pkttype == PKT_PUBLIC_KEY ) continue; /* oops, not for a main key */ /* we check until we find a valid keybinding */ rc = check_key_signature( keyblock, node, NULL ); if( !rc ) { if( opt.verbose ) log_info(_( "key %08lX.%lu: Good subkey binding\n"), (ulong)keyid_from_pk(pk,NULL), lid ); krec.r.key.keyflags |= KEYF_CHECKED | KEYF_VALID; } else { log_info(_( "key %08lX.%lu: Invalid subkey binding: %s\n"), (ulong)keyid_from_pk(pk,NULL), lid, g10_errstr(rc) ); krec.r.key.keyflags |= KEYF_CHECKED; krec.r.key.keyflags &= ~KEYF_VALID; } keybind_seen = 1; } else if( sig->sig_class == 0x20 && !revoke_seen ) { if( keynode->pkt->pkttype == PKT_PUBLIC_SUBKEY ) continue; /* a subkey is not expected here */ /* This is a key revocation certificate: check it */ rc = check_key_signature( keyblock, node, NULL ); if( !rc ) { if( opt.verbose ) log_info(_( "key %08lX.%lu: Valid key revocation\n"), (ulong)keyid_from_pk(pk,NULL), lid ); krec.r.key.keyflags |= KEYF_REVOKED; } else { log_info(_( "key %08lX.%lu: Invalid key revocation: %s\n"), (ulong)keyid_from_pk(pk,NULL), lid, g10_errstr(rc) ); } revoke_seen = 1; } else if( sig->sig_class == 0x28 && !revoke_seen ) { if( keynode->pkt->pkttype == PKT_PUBLIC_KEY ) continue; /* a mainkey is not expected here */ /* This is a subkey revocation certificate: check it */ /* fixme: we should also check the revocation * is newer than the key (OpenPGP) */ rc = check_key_signature( keyblock, node, NULL ); if( !rc ) { if( opt.verbose ) log_info(_( "key %08lX.%lu: Valid subkey revocation\n"), (ulong)keyid_from_pk(pk,NULL), lid ); krec.r.key.keyflags |= KEYF_REVOKED; } else { log_info(_( "key %08lX.%lu: Invalid subkey binding: %s\n"), (ulong)keyid_from_pk(pk,NULL), lid, g10_errstr(rc) ); } revoke_seen = 1; } } write_record( &krec ); } /**************** * This function checks the given user ID and inserts or updates * the uid record of the trustdb. Revocation certificates * are handled here. * * keyblock points to the first node in the keyblock, * uidnode is the node with the user id to check * keyid is the keyid of * the primary key, drec is the directory record and recno_list * is a list used to keep track of visited records. * Existing uidflags are recalculated if recheck is true. */ static void upd_uid_record( KBNODE keyblock, KBNODE uidnode, u32 *keyid, TRUSTREC *drec, RECNO_LIST *recno_list, int recheck ) { ulong lid = drec->recnum; PKT_user_id *uid = uidnode->pkt->pkt.user_id; TRUSTREC urec; PKT_signature *selfsig = NULL; byte uidhash[20]; KBNODE node; ulong recno, newrecno; int rc; /* see whether we already have an uid record */ rmd160_hash_buffer( uidhash, uid->name, uid->len ); for( recno=drec->r.dir.uidlist; recno; recno = urec.r.uid.next ) { read_record( recno, &urec, RECTYPE_UID ); if( !memcmp( uidhash, urec.r.uid.namehash, 20 ) ) break; } if( recno ) { /* we already have this record */ ins_recno_list( recno_list, recno, RECTYPE_UID ); } else { /* new user id */ recheck = 1; memset( &urec, 0 , sizeof(urec) ); urec.rectype = RECTYPE_UID; urec.r.uid.lid = drec->recnum; memcpy(urec.r.uid.namehash, uidhash, 20 ); urec.recnum = newrecno = tdbio_new_recnum(); write_record( &urec ); ins_recno_list( recno_list, newrecno, RECTYPE_UID ); /* and put this new record at the end of the uidlist */ if( !(recno=drec->r.dir.uidlist) ) { /* this is the first uid */ drec->r.dir.uidlist = newrecno; drec->dirty = 1; } else { /* we already have an uid, append it to the list */ TRUSTREC save = urec; for( ; recno; recno = urec.r.key.next ) read_record( recno, &urec, RECTYPE_UID ); urec.r.uid.next = newrecno; write_record( &urec ); urec = save; } } if( recheck || !(urec.r.uid.uidflags & UIDF_CHECKED) ) { /* check self signatures */ urec.r.uid.uidflags = 0; for( node=uidnode->next; node; node = node->next ) { PKT_signature *sig; if( node->pkt->pkttype == PKT_USER_ID ) break; /* ready */ if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) break; /* ready */ if( node->pkt->pkttype != PKT_SIGNATURE ) continue; sig = node->pkt->pkt.signature; if( keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1] ) continue; /* not a self signature */ if( (sig->sig_class&~3) == 0x10 ) { /* regular self signature */ rc = check_key_signature( keyblock, node, NULL ); if( !rc ) { if( opt.verbose ) log_info( "uid %08lX.%lu/%02X%02X: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], _("Good self-signature") ); urec.r.uid.uidflags |= UIDF_CHECKED | UIDF_VALID; if( !selfsig ) selfsig = sig; /* use the first valid sig */ } else { log_info( "uid %08lX/%02X%02X: %s: %s\n", (ulong)keyid[1], uidhash[18], uidhash[19], _("Invalid self-signature"), g10_errstr(rc) ); urec.r.uid.uidflags |= UIDF_CHECKED; } } else if( sig->sig_class == 0x30 ) { /* cert revocation */ rc = check_key_signature( keyblock, node, NULL ); if( !rc ) { if( opt.verbose ) log_info( "uid %08lX.%lu/%02X%02X: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], _("Valid user ID revocation\n") ); urec.r.uid.uidflags |= UIDF_CHECKED | UIDF_VALID; urec.r.uid.uidflags |= UIDF_REVOKED; } else { log_info("uid %08lX/%02X%02X: %s: %s\n", (ulong)keyid[1], uidhash[18], uidhash[19], _("Invalid user ID revocation"), g10_errstr(rc) ); } } } write_record( &urec ); } /* end check self-signatures */ if( (urec.r.uid.uidflags & (UIDF_CHECKED|UIDF_VALID)) != (UIDF_CHECKED|UIDF_VALID) ) return; /* user ID is not valid, so no need to check more things */ /* check the preferences */ if( selfsig ) upd_pref_record( &urec, keyid, selfsig ); /* check non-self signatures */ for( node=uidnode->next; node; node = node->next ) { PKT_signature *sig; if( node->pkt->pkttype == PKT_USER_ID ) break; /* ready */ if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) break; /* ready */ if( node->pkt->pkttype != PKT_SIGNATURE ) continue; sig = node->pkt->pkt.signature; if( keyid[0] == sig->keyid[0] || keyid[1] == sig->keyid[1] ) continue; /* skip self signature */ if( (sig->sig_class&~3) == 0x10 ) { /* regular certification */ upd_cert_record( keyblock, node, keyid, drec, recno_list, recheck, &urec, uidhash, 0 ); } else if( sig->sig_class == 0x30 ) { /* cert revocation */ upd_cert_record( keyblock, node, keyid, drec, recno_list, recheck, &urec, uidhash, 1 ); } } /* end check certificates */ write_record( &urec ); } /**************** * * */ static void upd_pref_record( TRUSTREC *urec, u32 *keyid, PKT_signature *sig ) { static struct { sigsubpkttype_t subpkttype; int preftype; } ptable[] = { { SIGSUBPKT_PREF_SYM, PREFTYPE_SYM }, { SIGSUBPKT_PREF_HASH, PREFTYPE_HASH }, { SIGSUBPKT_PREF_COMPR, PREFTYPE_COMPR }, { 0, 0 } }; TRUSTREC prec; ulong lid = urec->r.uid.lid ; const byte *uidhash = urec->r.uid.namehash; const byte *s; size_t n; int k, i; ulong recno; byte prefs_sig[200]; int n_prefs_sig = 0; byte prefs_rec[200]; int n_prefs_rec = 0; /* check for changed preferences */ for(k=0; ptable[k].subpkttype; k++ ) { s = parse_sig_subpkt2( sig, ptable[k].subpkttype, &n ); if( s ) { if( n_prefs_sig >= DIM(prefs_sig)-1 ) { log_info("uid %08lX.%lu/%02X%02X: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], _("Too many preferences") ); break; } prefs_sig[n_prefs_sig++] = ptable[k].preftype; prefs_sig[n_prefs_sig++] = *s; } } for( recno=urec->r.uid.prefrec; recno; recno = prec.r.pref.next ) { read_record( recno, &prec, RECTYPE_PREF ); for(i = 0; i < ITEMS_PER_PREF_RECORD; i +=2 ) { if( n_prefs_rec >= DIM(prefs_rec)-1 ) { log_info("uid %08lX.%lu/%02X%02X: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], _("Too many preference items") ); break; } prefs_rec[n_prefs_rec++] = prec.r.pref.data[i]; prefs_rec[n_prefs_rec++] = prec.r.pref.data[i+1]; } } if( n_prefs_sig == n_prefs_rec && !memcmp( prefs_sig, prefs_rec, n_prefs_sig ) ) return; /* not chnaged */ /* Preferences have changed: Delete all pref records * This is much simpler than checking whether we have to * do update the record at all - the record cache may care about it */ for( recno=urec->r.uid.prefrec; recno; recno = prec.r.pref.next ) { read_record( recno, &prec, RECTYPE_PREF ); delete_record( recno ); } if( n_prefs_sig > ITEMS_PER_PREF_RECORD ) log_info(_("WARNING: can't yet handle long pref records\n")); memset( &prec, 0, sizeof prec ); prec.recnum = tdbio_new_recnum(); prec.rectype = RECTYPE_PREF; prec.r.pref.lid = lid; if( n_prefs_sig <= ITEMS_PER_PREF_RECORD ) memcpy( prec.r.pref.data, prefs_sig, n_prefs_sig ); else { /* need more than one pref record */ TRUSTREC tmp; ulong nextrn; int n = n_prefs_sig; byte *pp = prefs_sig; memcpy( prec.r.pref.data, pp, ITEMS_PER_PREF_RECORD ); n -= ITEMS_PER_PREF_RECORD; pp += ITEMS_PER_PREF_RECORD; nextrn = prec.r.pref.next = tdbio_new_recnum(); do { memset( &tmp, 0, sizeof tmp ); tmp.recnum = nextrn; tmp.rectype = RECTYPE_PREF; tmp.r.pref.lid = lid; if( n <= ITEMS_PER_PREF_RECORD ) { memcpy( tmp.r.pref.data, pp, n ); n = 0; } else { memcpy( tmp.r.pref.data, pp, ITEMS_PER_PREF_RECORD ); n -= ITEMS_PER_PREF_RECORD; pp += ITEMS_PER_PREF_RECORD; nextrn = tmp.r.pref.next = tdbio_new_recnum(); } write_record( &tmp ); } while( n ); } write_record( &prec ); urec->r.uid.prefrec = prec.recnum; urec->dirty = 1; } static void upd_cert_record( KBNODE keyblock, KBNODE signode, u32 *keyid, TRUSTREC *drec, RECNO_LIST *recno_list, int recheck, TRUSTREC *urec, const byte *uidhash, int revoke ) { /* We simply insert the signature into the sig records but * avoid duplicate ones. We do not check them here because * there is a big chance, that we import required public keys * later. The problem with this is that we must somewhere store * the information about this signature (we need a record id). * We do this by using the record type shadow dir, which will * be converted to a dir record as soon as a new public key is * inserted into the trustdb. */ ulong lid = drec->recnum; PKT_signature *sig = signode->pkt->pkt.signature; TRUSTREC rec; ulong recno; TRUSTREC delrec; int delrecidx=0; int newflag = 0; ulong newlid = 0; PKT_public_key *pk = m_alloc_clear( sizeof *pk ); ulong pk_lid = 0; int found_sig = 0; int found_delrec = 0; int rc; delrec.recnum = 0; /* get the LID of the pubkey of the signature under verification */ rc = get_pubkey( pk, sig->keyid ); if( !rc ) { if( pk->local_id ) pk_lid = pk->local_id; else { rc = tdbio_search_dir_bypk( pk, &rec ); if( !rc ) pk_lid = rec.recnum; else if( rc == -1 ) { /* see whether there is a sdir instead */ u32 akid[2]; keyid_from_pk( pk, akid ); rc = tdbio_search_sdir( akid, pk->pubkey_algo, &rec ); if( !rc ) pk_lid = rec.recnum; } } } free_public_key( pk ); pk = NULL; /* Loop over all signatures just in case one is not correctly * marked. If we see the correct signature, set a flag. * delete duplicate signatures (should not happen but...) */ for( recno = urec->r.uid.siglist; recno; recno = rec.r.sig.next ) { int i; read_record( recno, &rec, RECTYPE_SIG ); for(i=0; i < SIGS_PER_RECORD; i++ ) { TRUSTREC tmp; if( !rec.r.sig.sig[i].lid ) { if( !found_delrec && !delrec.recnum ) { delrec = rec; delrecidx = i; found_delrec=1; } continue; /* skip deleted sigs */ } if( rec.r.sig.sig[i].lid == pk_lid ) { if( found_sig ) { log_info( "sig %08lX.%lu/%02X%02X/%08lX: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], (ulong)sig->keyid[1], _("Duplicated certificate - deleted") ); rec.r.sig.sig[i].lid = 0; rec.dirty = 1; continue; } found_sig = 1; } if( !recheck && !revoke && (rec.r.sig.sig[i].flag & SIGF_CHECKED) ) continue; /* we already checked this signature */ if( !recheck && (rec.r.sig.sig[i].flag & SIGF_NOPUBKEY) ) continue; /* we do not have the public key */ read_record( rec.r.sig.sig[i].lid, &tmp, 0 ); if( tmp.rectype == RECTYPE_DIR ) { /* In this case we should now be able to check the signature */ rc = check_key_signature( keyblock, signode, NULL ); if( !rc ) { /* valid signature */ if( opt.verbose ) log_info("sig %08lX.%lu/%02X%02X/%08lX: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], (ulong)sig->keyid[1], revoke? _("Valid certificate revocation") : _("Good certificate") ); rec.r.sig.sig[i].flag = SIGF_CHECKED | SIGF_VALID; if( revoke ) rec.r.sig.sig[i].flag |= SIGF_REVOKED; } else if( rc == G10ERR_NO_PUBKEY ) { if( (rec.r.sig.sig[i].flag & SIGF_CHECKED) ) log_info("sig %08lX.%lu/%02X%02X/%08lX: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], (ulong)sig->keyid[1], _("Hmmm, public key lost?") ); rec.r.sig.sig[i].flag = SIGF_NOPUBKEY; if( revoke ) rec.r.sig.sig[i].flag |= SIGF_REVOKED; } else { log_info("sig %08lX.%lu/%02X%02X/%08lX: %s: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], (ulong)sig->keyid[1], revoke? _("Invalid certificate revocation") : _("Invalid certificate"), g10_errstr(rc)); rec.r.sig.sig[i].flag = SIGF_CHECKED; if( revoke ) rec.r.sig.sig[i].flag |= SIGF_REVOKED; } rec.dirty = 1; } else if( tmp.rectype == RECTYPE_SDIR ) { /* must check that it is the right one */ if( tmp.r.sdir.keyid[0] == sig->keyid[0] && tmp.r.sdir.keyid[1] == sig->keyid[1] && (!tmp.r.sdir.pubkey_algo || tmp.r.sdir.pubkey_algo == sig->pubkey_algo )) { if( !(rec.r.sig.sig[i].flag & SIGF_NOPUBKEY) ) log_info(_("uid %08lX.%lu/%02X%02X: " "has shadow dir %lu but is not yet marked.\n"), (ulong)keyid[1], lid, uidhash[18], uidhash[19], tmp.recnum ); rec.r.sig.sig[i].flag = SIGF_NOPUBKEY; if( revoke ) rec.r.sig.sig[i].flag |= SIGF_REVOKED; rec.dirty = 1; /* fixme: should we verify that the record is * in the hintlist? - This case here should anyway * never occur */ } } else { log_error(_("sig record %lu[%d] points to wrong record.\n"), rec.r.sig.sig[i].lid, i ); die_invalid_db(); } } if( found_delrec && delrec.recnum ) { delrec = rec; found_delrec = 0; /* we only want the first one */ } if( rec.dirty ) { write_record( &rec ); rec.dirty = 0; } } if( found_sig ) return; /* at this point, we have verified, that the signature is not in * our list of signatures. Add a new record with that signature * and if the public key is there, check the signature. */ if( !pk_lid ) /* we have already seen that there is no pubkey */ rc = G10ERR_NO_PUBKEY; else rc = check_key_signature( keyblock, signode, NULL ); if( !rc ) { /* valid signature */ if( opt.verbose ) log_info("sig %08lX.%lu/%02X%02X/%08lX: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], (ulong)sig->keyid[1], revoke? _("Valid certificate revocation") : _("Good certificate") ); newlid = pk_lid; /* this is the pk of the signature */ newflag = SIGF_CHECKED | SIGF_VALID; if( revoke ) newflag |= SIGF_REVOKED; } else if( rc == G10ERR_NO_PUBKEY ) { if( opt.verbose > 1 ) log_info("sig %08lX.%lu/%02X%02X/%08lX: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], (ulong)sig->keyid[1], g10_errstr(rc) ); newlid = create_shadow_dir( sig, lid ); newflag = SIGF_NOPUBKEY; if( revoke ) newflag |= SIGF_REVOKED; } else { log_info( "sig %08lX.%lu/%02X%02X/%08lX: %s: %s\n", (ulong)keyid[1], lid, uidhash[18], uidhash[19], (ulong)sig->keyid[1], revoke? _("Invalid certificate revocation") : _("Invalid certificate"), g10_errstr(rc)); newlid = create_shadow_dir( sig, lid ); newflag = SIGF_CHECKED; if( revoke ) newflag |= SIGF_REVOKED; } if( delrec.recnum ) { /* we can reuse a deleted/unused slot */ delrec.r.sig.sig[delrecidx].lid = newlid; delrec.r.sig.sig[delrecidx].flag= newflag; write_record( &delrec ); } else { /* must insert a new sig record */ TRUSTREC tmp; memset( &tmp, 0, sizeof tmp ); tmp.recnum = tdbio_new_recnum(); tmp.rectype = RECTYPE_SIG; tmp.r.sig.lid = lid; tmp.r.sig.next = urec->r.uid.siglist; tmp.r.sig.sig[0].lid = newlid; tmp.r.sig.sig[0].flag= newflag; write_record( &tmp ); urec->r.uid.siglist = tmp.recnum; urec->dirty = 1; } } /**************** * Update all the info from the public keyblock. * The key must already exist in the keydb. * This function is responsible for checking the signatures in cases * where the public key is already available. If we do not have the public * key, the check is done by some special code in insert_trust_record(). */ int update_trust_record( KBNODE keyblock, int recheck, int *modified ) { PKT_public_key *primary_pk; KBNODE node; TRUSTREC drec; TRUSTREC krec; TRUSTREC urec; TRUSTREC prec; TRUSTREC helprec; int rc = 0; u32 keyid[2]; /* keyid of primary key */ ulong recno, lastrecno; RECNO_LIST recno_list = NULL; /* list of verified records */ /* fixme: replace recno_list by a lookup on node->recno */ if( modified ) *modified = 0; node = find_kbnode( keyblock, PKT_PUBLIC_KEY ); primary_pk = node->pkt->pkt.public_key; rc = get_dir_record( primary_pk, &drec ); if( rc ) return rc; if( !primary_pk->local_id ) primary_pk->local_id = drec.recnum; keyid_from_pk( primary_pk, keyid ); /* fixme: check that the keyblock has a valid structure */ rc = tdbio_begin_transaction(); if( rc ) return rc; /* update the keys */ for( node=keyblock; node; node = node->next ) { if( node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) upd_key_record( keyblock, node, keyid, &drec, &recno_list, recheck ); } /* update the user IDs */ for( node=keyblock; node; node = node->next ) { if( node->pkt->pkttype == PKT_USER_ID ) upd_uid_record( keyblock, node, keyid, &drec, &recno_list, recheck ); } /* delete keyrecords from the trustdb which are not anymore used */ /* should we really do this, or is it better to keep them and */ /* mark as unused? */ lastrecno = 0; for( recno=drec.r.dir.keylist; recno; recno = krec.r.key.next ) { read_record( recno, &krec, RECTYPE_KEY ); if( !qry_recno_list( recno_list, recno, RECTYPE_KEY ) ) { /* delete this one */ if( !lastrecno ) { drec.r.dir.keylist = krec.r.key.next; drec.dirty = 1; } else { read_record( lastrecno, &helprec, RECTYPE_KEY ); helprec.r.key.next = krec.r.key.next; write_record( &helprec ); } delete_record( recno ); } else lastrecno = recno; } /* delete uid records and sig and their pref records from the * trustdb which are not anymore used */ lastrecno = 0; for( recno=drec.r.dir.uidlist; recno; recno = urec.r.uid.next ) { read_record( recno, &urec, RECTYPE_UID ); if( !qry_recno_list( recno_list, recno, RECTYPE_UID ) ) { ulong r2; /* delete this one */ if( !lastrecno ) { drec.r.dir.uidlist = urec.r.uid.next; drec.dirty = 1; } else { read_record( lastrecno, &helprec, RECTYPE_UID ); helprec.r.uid.next = urec.r.uid.next; write_record( &helprec ); } for(r2=urec.r.uid.prefrec ; r2; r2 = prec.r.pref.next ) { read_record( r2, &prec, RECTYPE_PREF ); delete_record( r2 ); } for(r2=urec.r.uid.siglist ; r2; r2 = helprec.r.sig.next ) { read_record( r2, &helprec, RECTYPE_SIG ); delete_record( r2 ); } delete_record( recno ); } else lastrecno = recno; } if( rc ) rc = tdbio_cancel_transaction(); else { drec.r.dir.dirflags |= DIRF_CHECKED; drec.r.dir.dirflags &= ~DIRF_VALVALID; write_record( &drec ); if( modified && tdbio_is_dirty() ) *modified = 1; rc = tdbio_end_transaction(); } rel_recno_list( &recno_list ); return rc; } /**************** * Insert a trust record into the TrustDB * This function assumes that the record does not yet exist. */ int insert_trust_record( PKT_public_key *pk ) { TRUSTREC dirrec; TRUSTREC shadow; KBNODE keyblock = NULL; KBNODE node; byte fingerprint[MAX_FINGERPRINT_LEN]; size_t fingerlen; int rc = 0; ulong hintlist = 0; if( pk->local_id ) log_bug("pk->local_id=%lu\n", pk->local_id ); fingerprint_from_pk( pk, fingerprint, &fingerlen ); /* fixme: assert that we do not have this record. * we can do this by searching for the primary keyid * * fixme: If there is no such key we should look whether one * of the subkeys has been used to sign another key and in this case * we got the key anyway. Because a secondary key can't be used * without a primary key (it is needed to bind the secondary one * to the primary one which has the user ids etc.) */ /* get the keyblock which has the key */ rc = get_keyblock_byfprint( &keyblock, fingerprint, fingerlen ); if( rc ) { /* that should never happen */ log_error( _("insert_trust_record: keyblock not found: %s\n"), g10_errstr(rc) ); goto leave; } /* check that we used the primary key (we are little bit paranoid) */ { PKT_public_key *a_pk; u32 akid[2], bkid[2]; node = find_kbnode( keyblock, PKT_PUBLIC_KEY ); a_pk = node->pkt->pkt.public_key; /* we can't use cmp_public_keys here because some parts (expiredate) * might not be set in pk <--- but why (fixme) */ keyid_from_pk( a_pk, akid ); keyid_from_pk( pk, bkid ); if( akid[0] != bkid[0] || akid[1] != bkid[1] ) { log_error(_("did not use primary key for insert_trust_record()\n")); rc = G10ERR_GENERAL; goto leave; } } /* We have to look for a shadow dir record which must be reused * as the dir record. And: check all signatures which are listed * in the hintlist of the shadow dir record. */ rc = tdbio_search_sdir( pk->keyid, pk->pubkey_algo, &shadow ); if( rc && rc != -1 ) { log_error(_("tdbio_search_dir failed: %s\n"), g10_errstr(rc)); die_invalid_db(); } memset( &dirrec, 0, sizeof dirrec ); dirrec.rectype = RECTYPE_DIR; if( !rc ) { /* hey, great: this key has already signed other keys * convert this to a real directory entry */ hintlist = shadow.r.sdir.hintlist; dirrec.recnum = shadow.recnum; } else { dirrec.recnum = tdbio_new_recnum(); } dirrec.r.dir.lid = dirrec.recnum; write_record( &dirrec ); /* store the LID */ pk->local_id = dirrec.r.dir.lid; for( node=keyblock; node; node = node->next ) { if( node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY ) { PKT_public_key *pk = node->pkt->pkt.public_key; pk->local_id = dirrec.r.dir.lid; } else if( node->pkt->pkttype == PKT_SIGNATURE ) { PKT_signature *sig = node->pkt->pkt.signature; sig->local_id = dirrec.r.dir.lid; } } /* and put all the other stuff into the keydb */ rc = update_trust_record( keyblock, 0, NULL ); if( !rc ) process_hintlist( hintlist, dirrec.r.dir.lid ); leave: if( rc && hintlist ) ; /* fixme: the hintlist is not anymore anchored */ release_kbnode( keyblock ); do_sync(); return rc; } int update_ownertrust( ulong lid, unsigned new_trust ) { TRUSTREC rec; read_record( lid, &rec, RECTYPE_DIR ); rec.r.dir.ownertrust = new_trust; write_record( &rec ); do_sync(); return 0; }