Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F22067738
tofu.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
122 KB
Subscribers
None
tofu.c
View Options
/* tofu.c - TOFU trust model.
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*/
/* TODO:
- Format the fingerprints nicely when printing (similar to gpg
--list-keys)
*/
#include
<config.h>
#include
<stdio.h>
#include
<sys/stat.h>
#include
<stdarg.h>
#include
<sqlite3.h>
#include
<time.h>
#include
"gpg.h"
#include
"../common/types.h"
#include
"../common/logging.h"
#include
"../common/stringhelp.h"
#include
"options.h"
#include
"../common/mbox-util.h"
#include
"../common/i18n.h"
#include
"../common/ttyio.h"
#include
"trustdb.h"
#include
"../common/mkdir_p.h"
#include
"gpgsql.h"
#include
"../common/status.h"
#include
"tofu.h"
#define CONTROL_L ('L' - 'A' + 1)
/* Number of days with signed / ecnrypted messages required to
* indicate that enough history is available for basic trust. */
#define BASIC_TRUST_THRESHOLD 4
/* Number of days with signed / encrypted messages required to
* indicate that a lot of history is available. */
#define FULL_TRUST_THRESHOLD 21
/* A struct with data pertaining to the tofu DB. There is one such
struct per session and it is cached in session's ctrl structure.
To initialize this or get the current singleton, call opendbs().
There is no need to explicitly release it; cleanup is done when the
CTRL object is released. */
struct
tofu_dbs_s
{
sqlite3
*
db
;
char
*
want_lock_file
;
time_t
want_lock_file_ctime
;
struct
{
sqlite3_stmt
*
savepoint_batch
;
sqlite3_stmt
*
savepoint_batch_commit
;
sqlite3_stmt
*
record_binding_get_old_policy
;
sqlite3_stmt
*
record_binding_update
;
sqlite3_stmt
*
get_policy_select_policy_and_conflict
;
sqlite3_stmt
*
get_trust_bindings_with_this_email
;
sqlite3_stmt
*
get_trust_gather_other_user_ids
;
sqlite3_stmt
*
get_trust_gather_signature_stats
;
sqlite3_stmt
*
get_trust_gather_encryption_stats
;
sqlite3_stmt
*
register_already_seen
;
sqlite3_stmt
*
register_signature
;
sqlite3_stmt
*
register_encryption
;
}
s
;
int
in_batch_transaction
;
int
in_transaction
;
time_t
batch_update_started
;
};
#define STRINGIFY(s) STRINGIFY2(s)
#define STRINGIFY2(s) #s
/* The grouping parameters when collecting signature statistics. */
/* If a message is signed a couple of hours in the future, just assume
some clock skew. */
#define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60)
/* Days. */
#define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
#define TIME_AGO_SMALL_THRESHOLD (7 * TIME_AGO_UNIT_SMALL)
/* Months. */
#define TIME_AGO_UNIT_MEDIUM (30 * 24 * 60 * 60)
#define TIME_AGO_MEDIUM_THRESHOLD (2 * TIME_AGO_UNIT_MEDIUM)
/* Years. */
#define TIME_AGO_UNIT_LARGE (365 * 24 * 60 * 60)
#define TIME_AGO_LARGE_THRESHOLD (2 * TIME_AGO_UNIT_LARGE)
/* Local prototypes. */
static
gpg_error_t
end_transaction
(
ctrl_t
ctrl
,
int
only_batch
);
static
char
*
email_from_user_id
(
const
char
*
user_id
);
static
int
show_statistics
(
tofu_dbs_t
dbs
,
const
char
*
fingerprint
,
const
char
*
email
,
enum
tofu_policy
policy
,
estream_t
outfp
,
int
only_status_fd
,
time_t
now
);
const
char
*
tofu_policy_str
(
enum
tofu_policy
policy
)
{
switch
(
policy
)
{
case
TOFU_POLICY_NONE
:
return
"none"
;
case
TOFU_POLICY_AUTO
:
return
"auto"
;
case
TOFU_POLICY_GOOD
:
return
"good"
;
case
TOFU_POLICY_UNKNOWN
:
return
"unknown"
;
case
TOFU_POLICY_BAD
:
return
"bad"
;
case
TOFU_POLICY_ASK
:
return
"ask"
;
default
:
return
"???"
;
}
}
/* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level
(e.g., TRUST_BAD) in light of the current configuration. */
int
tofu_policy_to_trust_level
(
enum
tofu_policy
policy
)
{
if
(
policy
==
TOFU_POLICY_AUTO
)
/* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY. */
policy
=
opt
.
tofu_default_policy
;
switch
(
policy
)
{
case
TOFU_POLICY_AUTO
:
/* If POLICY and OPT.TOFU_DEFAULT_POLICY are both AUTO, default
to marginal trust. */
return
TRUST_MARGINAL
;
case
TOFU_POLICY_GOOD
:
return
TRUST_FULLY
;
case
TOFU_POLICY_UNKNOWN
:
return
TRUST_UNKNOWN
;
case
TOFU_POLICY_BAD
:
return
TRUST_NEVER
;
case
TOFU_POLICY_ASK
:
return
TRUST_UNKNOWN
;
default
:
log_bug
(
"Bad value for trust policy: %d
\n
"
,
opt
.
tofu_default_policy
);
return
0
;
}
}
/* Start a transaction on DB. If ONLY_BATCH is set, then this will
start a batch transaction if we haven't started a batch transaction
and one has been requested. */
static
gpg_error_t
begin_transaction
(
ctrl_t
ctrl
,
int
only_batch
)
{
tofu_dbs_t
dbs
=
ctrl
->
tofu
.
dbs
;
int
rc
;
char
*
err
=
NULL
;
log_assert
(
dbs
);
/* If we've been in batch update mode for a while (on average, more
* than 500 ms), to prevent starving other gpg processes, we drop
* and retake the batch lock.
*
* Note: gnupg_get_time has a one second resolution, if we wanted a
* higher resolution, we could use npth_clock_gettime. */
if
(
/* No real transactions. */
dbs
->
in_transaction
==
0
/* There is an open batch transaction. */
&&
dbs
->
in_batch_transaction
/* And some time has gone by since it was started. */
&&
dbs
->
batch_update_started
!=
gnupg_get_time
())
{
struct
stat
statbuf
;
/* If we are in a batch update, then batch updates better have
been enabled. */
log_assert
(
ctrl
->
tofu
.
batch_updated_wanted
);
/* Check if another process wants to run. (We just ignore any
* stat failure. A waiter might have to wait a bit longer, but
* otherwise there should be no impact.) */
if
(
gnupg_stat
(
dbs
->
want_lock_file
,
&
statbuf
)
==
0
&&
statbuf
.
st_ctime
!=
dbs
->
want_lock_file_ctime
)
{
end_transaction
(
ctrl
,
2
);
/* Yield to allow another process a chance to run. Note:
* testing suggests that anything less than a 100ms tends to
* not result in the other process getting the lock. */
gnupg_usleep
(
100000
);
}
else
dbs
->
batch_update_started
=
gnupg_get_time
();
}
if
(
/* We don't have an open batch transaction. */
!
dbs
->
in_batch_transaction
&&
(
/* Batch mode is enabled or we are starting a new transaction. */
ctrl
->
tofu
.
batch_updated_wanted
||
dbs
->
in_transaction
==
0
))
{
struct
stat
statbuf
;
/* We are in batch mode, but we don't have an open batch
* transaction. Since the batch save point must be the outer
* save point, it must be taken before the inner save point. */
log_assert
(
dbs
->
in_transaction
==
0
);
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
savepoint_batch
,
NULL
,
NULL
,
&
err
,
"begin immediate transaction;"
,
GPGSQL_ARG_END
);
if
(
rc
)
{
log_error
(
_
(
"error beginning transaction on TOFU database: %s
\n
"
),
err
);
sqlite3_free
(
err
);
return
gpg_error
(
GPG_ERR_GENERAL
);
}
dbs
->
in_batch_transaction
=
1
;
dbs
->
batch_update_started
=
gnupg_get_time
();
if
(
gnupg_stat
(
dbs
->
want_lock_file
,
&
statbuf
)
==
0
)
dbs
->
want_lock_file_ctime
=
statbuf
.
st_ctime
;
}
if
(
only_batch
)
return
0
;
log_assert
(
dbs
->
in_transaction
>=
0
);
dbs
->
in_transaction
++
;
rc
=
gpgsql_exec_printf
(
dbs
->
db
,
NULL
,
NULL
,
&
err
,
"savepoint inner%d;"
,
dbs
->
in_transaction
);
if
(
rc
)
{
log_error
(
_
(
"error beginning transaction on TOFU database: %s
\n
"
),
err
);
sqlite3_free
(
err
);
return
gpg_error
(
GPG_ERR_GENERAL
);
}
return
0
;
}
/* Commit a transaction. If ONLY_BATCH is 1, then this only ends the
* batch transaction if we have left batch mode. If ONLY_BATCH is 2,
* this commits any open batch transaction even if we are still in
* batch mode. */
static
gpg_error_t
end_transaction
(
ctrl_t
ctrl
,
int
only_batch
)
{
tofu_dbs_t
dbs
=
ctrl
->
tofu
.
dbs
;
int
rc
;
char
*
err
=
NULL
;
if
(
only_batch
||
(
!
only_batch
&&
dbs
->
in_transaction
==
1
))
{
if
(
!
dbs
)
return
0
;
/* Shortcut to allow for easier cleanup code. */
/* If we are releasing the batch transaction, then we better not
be in a normal transaction. */
if
(
only_batch
)
log_assert
(
dbs
->
in_transaction
==
0
);
if
(
/* Batch mode disabled? */
(
!
ctrl
->
tofu
.
batch_updated_wanted
||
only_batch
==
2
)
/* But, we still have an open batch transaction? */
&&
dbs
->
in_batch_transaction
)
{
/* The batch transaction is still in open, but we've left
* batch mode. */
dbs
->
in_batch_transaction
=
0
;
dbs
->
in_transaction
=
0
;
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
savepoint_batch_commit
,
NULL
,
NULL
,
&
err
,
"commit transaction;"
,
GPGSQL_ARG_END
);
if
(
rc
)
{
log_error
(
_
(
"error committing transaction on TOFU database: %s
\n
"
),
err
);
sqlite3_free
(
err
);
return
gpg_error
(
GPG_ERR_GENERAL
);
}
return
0
;
}
if
(
only_batch
)
return
0
;
}
log_assert
(
dbs
);
log_assert
(
dbs
->
in_transaction
>
0
);
rc
=
gpgsql_exec_printf
(
dbs
->
db
,
NULL
,
NULL
,
&
err
,
"release inner%d;"
,
dbs
->
in_transaction
);
dbs
->
in_transaction
--
;
if
(
rc
)
{
log_error
(
_
(
"error committing transaction on TOFU database: %s
\n
"
),
err
);
sqlite3_free
(
err
);
return
gpg_error
(
GPG_ERR_GENERAL
);
}
return
0
;
}
static
gpg_error_t
rollback_transaction
(
ctrl_t
ctrl
)
{
tofu_dbs_t
dbs
=
ctrl
->
tofu
.
dbs
;
int
rc
;
char
*
err
=
NULL
;
log_assert
(
dbs
);
log_assert
(
dbs
->
in_transaction
>
0
);
/* Be careful to not undo any progress made by closed transactions in
batch mode. */
rc
=
gpgsql_exec_printf
(
dbs
->
db
,
NULL
,
NULL
,
&
err
,
"rollback to inner%d;"
,
dbs
->
in_transaction
);
dbs
->
in_transaction
--
;
if
(
rc
)
{
log_error
(
_
(
"error rolling back transaction on TOFU database: %s
\n
"
),
err
);
sqlite3_free
(
err
);
return
gpg_error
(
GPG_ERR_GENERAL
);
}
return
0
;
}
void
tofu_begin_batch_update
(
ctrl_t
ctrl
)
{
ctrl
->
tofu
.
batch_updated_wanted
++
;
}
void
tofu_end_batch_update
(
ctrl_t
ctrl
)
{
log_assert
(
ctrl
->
tofu
.
batch_updated_wanted
>
0
);
ctrl
->
tofu
.
batch_updated_wanted
--
;
end_transaction
(
ctrl
,
1
);
}
/* Suspend any extant batch transaction (it is safe to call this even
no batch transaction has been started). Note: you cannot suspend a
batch transaction if you are in a normal transaction. The batch
transaction can be resumed explicitly by calling
tofu_resume_batch_transaction or implicitly by starting a normal
transaction. */
static
void
tofu_suspend_batch_transaction
(
ctrl_t
ctrl
)
{
end_transaction
(
ctrl
,
2
);
}
/* Resume a batch transaction if there is no extant batch transaction
and one has been requested using tofu_begin_batch_transaction. */
static
void
tofu_resume_batch_transaction
(
ctrl_t
ctrl
)
{
begin_transaction
(
ctrl
,
1
);
}
/* Wrapper around strtol which prints a warning in case of a
* conversion error. On success the converted value is stored at
* R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE
* and an error code is returned. */
static
gpg_error_t
string_to_long
(
long
*
r_value
,
const
char
*
string
,
long
fallback
,
int
line
)
{
gpg_error_t
err
;
char
*
tail
=
NULL
;
gpg_err_set_errno
(
0
);
*
r_value
=
strtol
(
string
,
&
tail
,
0
);
if
(
errno
||
!
(
!
strcmp
(
tail
,
".0"
)
||
!*
tail
))
{
err
=
errno
?
gpg_error_from_errno
(
errno
)
:
gpg_error
(
GPG_ERR_BAD_DATA
);
log_debug
(
"%s:%d: strtol failed for TOFU DB data; returned string"
" (string='%.10s%s'; tail='%.10s%s'): %s
\n
"
,
__FILE__
,
line
,
string
,
string
&&
strlen
(
string
)
>
10
?
"..."
:
""
,
tail
,
tail
&&
strlen
(
tail
)
>
10
?
"..."
:
""
,
gpg_strerror
(
err
));
*
r_value
=
fallback
;
}
else
err
=
0
;
return
err
;
}
/* Wrapper around strtoul which prints a warning in case of a
* conversion error. On success the converted value is stored at
* R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE
* and an error code is returned. */
static
gpg_error_t
string_to_ulong
(
unsigned
long
*
r_value
,
const
char
*
string
,
unsigned
long
fallback
,
int
line
)
{
gpg_error_t
err
;
char
*
tail
=
NULL
;
gpg_err_set_errno
(
0
);
*
r_value
=
strtoul
(
string
,
&
tail
,
0
);
if
(
errno
||
!
(
!
strcmp
(
tail
,
".0"
)
||
!*
tail
))
{
err
=
errno
?
gpg_error_from_errno
(
errno
)
:
gpg_error
(
GPG_ERR_BAD_DATA
);
log_debug
(
"%s:%d: strtoul failed for TOFU DB data; returned string"
" (string='%.10s%s'; tail='%.10s%s'): %s
\n
"
,
__FILE__
,
line
,
string
,
string
&&
strlen
(
string
)
>
10
?
"..."
:
""
,
tail
,
tail
&&
strlen
(
tail
)
>
10
?
"..."
:
""
,
gpg_strerror
(
err
));
*
r_value
=
fallback
;
}
else
err
=
0
;
return
err
;
}
/* Collect results of a select count (*) ...; style query. Aborts if
the argument is not a valid integer (or real of the form X.0). */
static
int
get_single_unsigned_long_cb
(
void
*
cookie
,
int
argc
,
char
**
argv
,
char
**
azColName
)
{
unsigned
long
int
*
count
=
cookie
;
(
void
)
azColName
;
log_assert
(
argc
==
1
);
if
(
string_to_ulong
(
count
,
argv
[
0
],
0
,
__LINE__
))
return
1
;
/* Abort. */
return
0
;
}
static
int
get_single_unsigned_long_cb2
(
void
*
cookie
,
int
argc
,
char
**
argv
,
char
**
azColName
,
sqlite3_stmt
*
stmt
)
{
(
void
)
stmt
;
return
get_single_unsigned_long_cb
(
cookie
,
argc
,
argv
,
azColName
);
}
/* We expect a single integer column whose name is "version". COOKIE
must point to an int. This function always aborts. On error or a
if the version is bad, sets *VERSION to -1. */
static
int
version_check_cb
(
void
*
cookie
,
int
argc
,
char
**
argv
,
char
**
azColName
)
{
int
*
version
=
cookie
;
if
(
argc
!=
1
||
strcmp
(
azColName
[
0
],
"version"
)
!=
0
)
{
*
version
=
-1
;
return
1
;
}
if
(
strcmp
(
argv
[
0
],
"1"
)
==
0
)
*
version
=
1
;
else
{
log_error
(
_
(
"unsupported TOFU database version: %s
\n
"
),
argv
[
0
]);
*
version
=
-1
;
}
/* Don't run again. */
return
1
;
}
static
int
check_utks
(
sqlite3
*
db
)
{
int
rc
;
char
*
err
=
NULL
;
struct
key_item
*
utks
;
struct
key_item
*
ki
;
int
utk_count
;
char
*
utks_string
=
NULL
;
char
keyid_str
[
16
+
1
];
long
utks_unchanged
=
0
;
/* An early version of the v1 format did not include the list of
* known ultimately trusted keys.
*
* This list is used to detect when the set of ultimately trusted
* keys changes. We need to detect this to invalidate the effective
* policy, which can change if an ultimately trusted key is added or
* removed. */
rc
=
sqlite3_exec
(
db
,
"create table if not exists ultimately_trusted_keys"
" (keyid);
\n
"
,
NULL
,
NULL
,
&
err
);
if
(
rc
)
{
log_error
(
"error creating 'ultimately_trusted_keys' TOFU table: %s
\n
"
,
err
);
sqlite3_free
(
err
);
goto
out
;
}
utks
=
tdb_utks
();
for
(
ki
=
utks
,
utk_count
=
0
;
ki
;
ki
=
ki
->
next
,
utk_count
++
)
;
if
(
utk_count
)
{
/* Build a list of keyids of the form "XXX","YYY","ZZZ". */
int
len
=
(
1
+
16
+
1
+
1
)
*
utk_count
;
int
o
=
0
;
utks_string
=
xmalloc
(
len
);
*
utks_string
=
0
;
for
(
ki
=
utks
,
utk_count
=
0
;
ki
;
ki
=
ki
->
next
,
utk_count
++
)
{
utks_string
[
o
++
]
=
'\''
;
format_keyid
(
ki
->
kid
,
KF_LONG
,
keyid_str
,
sizeof
(
keyid_str
));
memcpy
(
&
utks_string
[
o
],
keyid_str
,
16
);
o
+=
16
;
utks_string
[
o
++
]
=
'\''
;
utks_string
[
o
++
]
=
','
;
}
utks_string
[
o
-
1
]
=
0
;
log_assert
(
o
==
len
);
}
rc
=
gpgsql_exec_printf
(
db
,
get_single_unsigned_long_cb
,
&
utks_unchanged
,
&
err
,
"select"
/* Removed UTKs? (Known UTKs in current UTKs.) */
" ((select count(*) from ultimately_trusted_keys"
" where (keyid in (%s))) == %d)"
" and"
/* New UTKs? */
" ((select count(*) from ultimately_trusted_keys"
" where keyid not in (%s)) == 0);"
,
utks_string
?
utks_string
:
""
,
utk_count
,
utks_string
?
utks_string
:
""
);
xfree
(
utks_string
);
if
(
rc
)
{
log_error
(
_
(
"TOFU DB error"
));
print_further_info
(
"checking if ultimately trusted keys changed: %s"
,
err
);
sqlite3_free
(
err
);
goto
out
;
}
if
(
utks_unchanged
)
goto
out
;
if
(
DBG_TRUST
)
log_debug
(
"TOFU: ultimately trusted keys changed.
\n
"
);
/* Given that the set of ultimately trusted keys
* changed, clear any cached policies. */
rc
=
gpgsql_exec_printf
(
db
,
NULL
,
NULL
,
&
err
,
"update bindings set effective_policy = %d;"
,
TOFU_POLICY_NONE
);
if
(
rc
)
{
log_error
(
_
(
"TOFU DB error"
));
print_further_info
(
"clearing cached policies: %s"
,
err
);
sqlite3_free
(
err
);
goto
out
;
}
/* Now, update the UTK table. */
rc
=
sqlite3_exec
(
db
,
"drop table ultimately_trusted_keys;"
,
NULL
,
NULL
,
&
err
);
if
(
rc
)
{
log_error
(
_
(
"TOFU DB error"
));
print_further_info
(
"dropping ultimately_trusted_keys: %s"
,
err
);
sqlite3_free
(
err
);
goto
out
;
}
rc
=
sqlite3_exec
(
db
,
"create table if not exists"
" ultimately_trusted_keys (keyid);
\n
"
,
NULL
,
NULL
,
&
err
);
if
(
rc
)
{
log_error
(
_
(
"TOFU DB error"
));
print_further_info
(
"creating ultimately_trusted_keys: %s"
,
err
);
sqlite3_free
(
err
);
goto
out
;
}
for
(
ki
=
utks
;
ki
;
ki
=
ki
->
next
)
{
format_keyid
(
ki
->
kid
,
KF_LONG
,
keyid_str
,
sizeof
(
keyid_str
));
rc
=
gpgsql_exec_printf
(
db
,
NULL
,
NULL
,
&
err
,
"insert into ultimately_trusted_keys values ('%s');"
,
keyid_str
);
if
(
rc
)
{
log_error
(
_
(
"TOFU DB error"
));
print_further_info
(
"updating ultimately_trusted_keys: %s"
,
err
);
sqlite3_free
(
err
);
goto
out
;
}
}
out
:
return
rc
;
}
/* If the DB is new, initialize it. Otherwise, check the DB's
version.
Return 0 if the database is okay and 1 otherwise. */
static
int
initdb
(
sqlite3
*
db
)
{
char
*
err
=
NULL
;
int
rc
;
unsigned
long
int
count
;
int
version
=
-1
;
rc
=
sqlite3_exec
(
db
,
"begin transaction;"
,
NULL
,
NULL
,
&
err
);
if
(
rc
)
{
log_error
(
_
(
"error beginning transaction on TOFU database: %s
\n
"
),
err
);
sqlite3_free
(
err
);
return
1
;
}
/* If the DB has no tables, then assume this is a new DB that needs
to be initialized. */
rc
=
sqlite3_exec
(
db
,
"select count(*) from sqlite_master where type='table';"
,
get_single_unsigned_long_cb
,
&
count
,
&
err
);
if
(
rc
)
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
err
);
print_further_info
(
"query available tables"
);
sqlite3_free
(
err
);
goto
out
;
}
else
if
(
count
!=
0
)
/* Assume that the DB is already initialized. Make sure the
version is okay. */
{
rc
=
sqlite3_exec
(
db
,
"select version from version;"
,
version_check_cb
,
&
version
,
&
err
);
if
(
rc
==
SQLITE_ABORT
&&
version
==
1
)
/* Happy, happy, joy, joy. */
{
sqlite3_free
(
err
);
rc
=
0
;
goto
out
;
}
else
if
(
rc
==
SQLITE_ABORT
&&
version
==
-1
)
/* Unsupported version. */
{
/* An error message was already displayed. */
sqlite3_free
(
err
);
goto
out
;
}
else
if
(
rc
)
/* Some error. */
{
log_error
(
_
(
"error determining TOFU database's version: %s
\n
"
),
err
);
sqlite3_free
(
err
);
goto
out
;
}
else
{
/* Unexpected success. This can only happen if there are no
rows. (select returned 0, but expected ABORT.) */
log_error
(
_
(
"error determining TOFU database's version: %s
\n
"
),
gpg_strerror
(
GPG_ERR_NO_DATA
));
rc
=
1
;
goto
out
;
}
}
/* Create the version table. */
rc
=
sqlite3_exec
(
db
,
"create table version (version INTEGER);"
,
NULL
,
NULL
,
&
err
);
if
(
rc
)
{
log_error
(
_
(
"error initializing TOFU database: %s
\n
"
),
err
);
print_further_info
(
"create version"
);
sqlite3_free
(
err
);
goto
out
;
}
/* Initialize the version table, which contains a single integer
value. */
rc
=
sqlite3_exec
(
db
,
"insert into version values (1);"
,
NULL
,
NULL
,
&
err
);
if
(
rc
)
{
log_error
(
_
(
"error initializing TOFU database: %s
\n
"
),
err
);
print_further_info
(
"insert version"
);
sqlite3_free
(
err
);
goto
out
;
}
/* The list of <fingerprint, email> bindings and auxiliary data.
*
* OID is a unique ID identifying this binding (and used by the
* signatures table, see below). Note: OIDs will never be
* reused.
*
* FINGERPRINT: The key's fingerprint.
*
* EMAIL: The normalized email address.
*
* USER_ID: The unmodified user id from which EMAIL was extracted.
*
* TIME: The time this binding was first observed.
*
* POLICY: The trust policy (TOFU_POLICY_BAD, etc. as an integer).
*
* CONFLICT is either NULL or a fingerprint. Assume that we have
* a binding <0xdeadbeef, foo@example.com> and then we observe
* <0xbaddecaf, foo@example.com>. There two bindings conflict
* (they have the same email address). When we observe the
* latter binding, we warn the user about the conflict and ask
* for a policy decision about the new binding. We also change
* the old binding's policy to ask if it was auto. So that we
* know why this occurred, we also set conflict to 0xbaddecaf.
*/
rc
=
gpgsql_exec_printf
(
db
,
NULL
,
NULL
,
&
err
,
"create table bindings
\n
"
" (oid INTEGER PRIMARY KEY AUTOINCREMENT,
\n
"
" fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,
\n
"
" policy INTEGER CHECK (policy in (%d, %d, %d, %d, %d)),
\n
"
" conflict STRING,
\n
"
" unique (fingerprint, email));
\n
"
"create index bindings_fingerprint_email
\n
"
" on bindings (fingerprint, email);
\n
"
"create index bindings_email on bindings (email);
\n
"
,
TOFU_POLICY_AUTO
,
TOFU_POLICY_GOOD
,
TOFU_POLICY_UNKNOWN
,
TOFU_POLICY_BAD
,
TOFU_POLICY_ASK
);
if
(
rc
)
{
log_error
(
_
(
"error initializing TOFU database: %s
\n
"
),
err
);
print_further_info
(
"create bindings"
);
sqlite3_free
(
err
);
goto
out
;
}
/* The signatures that we have observed.
*
* BINDING refers to a record in the bindings table, which
* describes the binding (i.e., this is a foreign key that
* references bindings.oid).
*
* SIG_DIGEST is the digest stored in the signature.
*
* SIG_TIME is the timestamp stored in the signature.
*
* ORIGIN is a free-form string that describes who fed this
* signature to GnuPG (e.g., email:claws).
*
* TIME is the time this signature was registered. */
rc
=
sqlite3_exec
(
db
,
"create table signatures "
" (binding INTEGER NOT NULL, sig_digest TEXT,"
" origin TEXT, sig_time INTEGER, time INTEGER,"
" primary key (binding, sig_digest, origin));"
,
NULL
,
NULL
,
&
err
);
if
(
rc
)
{
log_error
(
_
(
"error initializing TOFU database: %s
\n
"
),
err
);
print_further_info
(
"create signatures"
);
sqlite3_free
(
err
);
goto
out
;
}
out
:
if
(
!
rc
)
{
/* Early version of the v1 format did not include the encryption
table. Add it. */
rc
=
sqlite3_exec
(
db
,
"create table if not exists encryptions"
" (binding INTEGER NOT NULL,"
" time INTEGER);"
"create index if not exists encryptions_binding"
" on encryptions (binding);
\n
"
,
NULL
,
NULL
,
&
err
);
if
(
rc
)
{
log_error
(
"error creating 'encryptions' TOFU table: %s
\n
"
,
err
);
sqlite3_free
(
err
);
}
}
if
(
!
rc
)
{
/* The effective policy for a binding. If a key is ultimately
* trusted, then the effective policy of all of its bindings is
* good. Likewise if a key is signed by an ultimately trusted
* key, etc. If the effective policy is NONE, then we need to
* recompute the effective policy. Otherwise, the effective
* policy is considered to be up to date, i.e., effective_policy
* is a cache of the computed policy. */
rc
=
gpgsql_exec_printf
(
db
,
NULL
,
NULL
,
&
err
,
"alter table bindings"
" add column effective_policy INTEGER"
" DEFAULT %d"
" CHECK (effective_policy in (%d, %d, %d, %d, %d, %d));"
,
TOFU_POLICY_NONE
,
TOFU_POLICY_NONE
,
TOFU_POLICY_AUTO
,
TOFU_POLICY_GOOD
,
TOFU_POLICY_UNKNOWN
,
TOFU_POLICY_BAD
,
TOFU_POLICY_ASK
);
if
(
rc
)
{
if
(
rc
==
SQLITE_ERROR
)
/* Almost certainly "duplicate column name", which we can
* safely ignore. */
rc
=
0
;
else
log_error
(
"adding column effective_policy to bindings DB: %s
\n
"
,
err
);
sqlite3_free
(
err
);
}
}
if
(
!
rc
)
rc
=
check_utks
(
db
);
if
(
rc
)
{
rc
=
sqlite3_exec
(
db
,
"rollback;"
,
NULL
,
NULL
,
&
err
);
if
(
rc
)
{
log_error
(
_
(
"error rolling back transaction on TOFU database: %s
\n
"
),
err
);
sqlite3_free
(
err
);
}
return
1
;
}
else
{
rc
=
sqlite3_exec
(
db
,
"end transaction;"
,
NULL
,
NULL
,
&
err
);
if
(
rc
)
{
log_error
(
_
(
"error committing transaction on TOFU database: %s
\n
"
),
err
);
sqlite3_free
(
err
);
return
1
;
}
return
0
;
}
}
static
int
busy_handler
(
void
*
cookie
,
int
call_count
)
{
ctrl_t
ctrl
=
cookie
;
tofu_dbs_t
dbs
=
ctrl
->
tofu
.
dbs
;
(
void
)
call_count
;
/* Update the want-lock-file time stamp (specifically, the ctime) so
* that the current owner knows that we (well, someone) want the
* lock. */
if
(
dbs
)
{
/* Note: we don't fail if we can't create the lock file: this
* process will have to wait a bit longer, but otherwise nothing
* horrible should happen. */
estream_t
fp
;
fp
=
es_fopen
(
dbs
->
want_lock_file
,
"w"
);
if
(
!
fp
)
log_debug
(
"TOFU: Error opening '%s': %s
\n
"
,
dbs
->
want_lock_file
,
strerror
(
errno
));
else
es_fclose
(
fp
);
}
/* Call again. */
return
1
;
}
/* Create a new DB handle. Returns NULL on error. */
/* FIXME: Change to return an error code for better reporting by the
caller. */
static
tofu_dbs_t
opendbs
(
ctrl_t
ctrl
)
{
char
*
filename
;
sqlite3
*
db
;
int
rc
;
if
(
!
ctrl
->
tofu
.
dbs
)
{
filename
=
make_filename
(
gnupg_homedir
(),
"tofu.db"
,
NULL
);
rc
=
sqlite3_open
(
filename
,
&
db
);
if
(
rc
)
{
log_error
(
_
(
"error opening TOFU database '%s': %s
\n
"
),
filename
,
sqlite3_errmsg
(
db
));
/* Even if an error occurs, DB is guaranteed to be valid. */
sqlite3_close
(
db
);
db
=
NULL
;
}
/* If a DB is locked wait up to 5 seconds for the lock to be cleared
before failing. */
if
(
db
)
{
sqlite3_busy_timeout
(
db
,
5
*
1000
);
sqlite3_busy_handler
(
db
,
busy_handler
,
ctrl
);
}
if
(
db
&&
initdb
(
db
))
{
sqlite3_close
(
db
);
db
=
NULL
;
}
if
(
db
)
{
ctrl
->
tofu
.
dbs
=
xmalloc_clear
(
sizeof
*
ctrl
->
tofu
.
dbs
);
ctrl
->
tofu
.
dbs
->
db
=
db
;
ctrl
->
tofu
.
dbs
->
want_lock_file
=
xasprintf
(
"%s-want-lock"
,
filename
);
}
xfree
(
filename
);
}
else
log_assert
(
ctrl
->
tofu
.
dbs
->
db
);
return
ctrl
->
tofu
.
dbs
;
}
/* Release all of the resources associated with the DB handle. */
void
tofu_closedbs
(
ctrl_t
ctrl
)
{
tofu_dbs_t
dbs
;
sqlite3_stmt
**
statements
;
dbs
=
ctrl
->
tofu
.
dbs
;
if
(
!
dbs
)
return
;
/* Not initialized. */
log_assert
(
dbs
->
in_transaction
==
0
);
end_transaction
(
ctrl
,
2
);
/* Arghh, that is a surprising use of the struct. */
for
(
statements
=
(
void
*
)
&
dbs
->
s
;
(
void
*
)
statements
<
(
void
*
)
&
(
&
dbs
->
s
)[
1
];
statements
++
)
sqlite3_finalize
(
*
statements
);
sqlite3_close
(
dbs
->
db
);
xfree
(
dbs
->
want_lock_file
);
xfree
(
dbs
);
ctrl
->
tofu
.
dbs
=
NULL
;
}
/* Collect results of a select min (foo) ...; style query. Aborts if
the argument is not a valid integer (or real of the form X.0). */
static
int
get_single_long_cb
(
void
*
cookie
,
int
argc
,
char
**
argv
,
char
**
azColName
)
{
long
*
count
=
cookie
;
(
void
)
azColName
;
log_assert
(
argc
==
1
);
if
(
string_to_long
(
count
,
argv
[
0
],
0
,
__LINE__
))
return
1
;
/* Abort. */
return
0
;
}
static
int
get_single_long_cb2
(
void
*
cookie
,
int
argc
,
char
**
argv
,
char
**
azColName
,
sqlite3_stmt
*
stmt
)
{
(
void
)
stmt
;
return
get_single_long_cb
(
cookie
,
argc
,
argv
,
azColName
);
}
/* Record (or update) a trust policy about a (possibly new)
binding.
If SHOW_OLD is set, the binding's old policy is displayed. */
static
gpg_error_t
record_binding
(
tofu_dbs_t
dbs
,
const
char
*
fingerprint
,
const
char
*
email
,
const
char
*
user_id
,
enum
tofu_policy
policy
,
enum
tofu_policy
effective_policy
,
const
char
*
conflict
,
int
set_conflict
,
int
show_old
,
time_t
now
)
{
char
*
fingerprint_pp
=
format_hexfingerprint
(
fingerprint
,
NULL
,
0
);
gpg_error_t
rc
;
char
*
err
=
NULL
;
if
(
!
(
policy
==
TOFU_POLICY_AUTO
||
policy
==
TOFU_POLICY_GOOD
||
policy
==
TOFU_POLICY_UNKNOWN
||
policy
==
TOFU_POLICY_BAD
||
policy
==
TOFU_POLICY_ASK
))
log_bug
(
"%s: Bad value for policy (%d)!
\n
"
,
__func__
,
policy
);
if
(
DBG_TRUST
||
show_old
)
{
/* Get the old policy. Since this is just for informational
* purposes, there is no need to start a transaction or to die
* if there is a failure. */
/* policy_old needs to be a long and not an enum tofu_policy,
because we pass it by reference to get_single_long_cb2, which
expects a long. */
long
policy_old
=
TOFU_POLICY_NONE
;
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
record_binding_get_old_policy
,
get_single_long_cb2
,
&
policy_old
,
&
err
,
"select policy from bindings where fingerprint = ? and email = ?"
,
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_STRING
,
email
,
GPGSQL_ARG_END
);
if
(
rc
)
{
log_debug
(
"TOFU: Error reading from binding database"
" (reading policy for <key: %s, user id: %s>): %s
\n
"
,
fingerprint
,
email
,
err
);
sqlite3_free
(
err
);
}
if
(
policy_old
!=
TOFU_POLICY_NONE
)
(
show_old
?
log_info
:
log_debug
)
(
"Changing TOFU trust policy for binding"
" <key: %s, user id: %s> from %s to %s.
\n
"
,
fingerprint
,
show_old
?
user_id
:
email
,
tofu_policy_str
(
policy_old
),
tofu_policy_str
(
policy
));
else
(
show_old
?
log_info
:
log_debug
)
(
"Setting TOFU trust policy for new binding"
" <key: %s, user id: %s> to %s.
\n
"
,
fingerprint
,
show_old
?
user_id
:
email
,
tofu_policy_str
(
policy
));
}
if
(
opt
.
dry_run
)
{
log_info
(
"TOFU database update skipped due to --dry-run
\n
"
);
rc
=
0
;
goto
leave
;
}
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
record_binding_update
,
NULL
,
NULL
,
&
err
,
"insert or replace into bindings
\n
"
" (oid, fingerprint, email, user_id, time,"
" policy, conflict, effective_policy)
\n
"
" values (
\n
"
/* If we don't explicitly reuse the OID, then SQLite will
* reallocate a new one. We just need to search for the OID
* based on the fingerprint and email since they are unique. */
" (select oid from bindings where fingerprint = ? and email = ?),
\n
"
" ?, ?, ?, ?, ?,"
/* If SET_CONFLICT is 0, then preserve conflict's current value. */
" case ?"
" when 0 then"
" (select conflict from bindings where fingerprint = ? and email = ?)"
" else ?"
" end,"
" ?);"
,
/* oid subquery. */
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_STRING
,
email
,
/* values 2 through 6. */
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_STRING
,
email
,
GPGSQL_ARG_STRING
,
user_id
,
GPGSQL_ARG_LONG_LONG
,
(
long
long
)
now
,
GPGSQL_ARG_INT
,
(
int
)
policy
,
/* conflict subquery. */
GPGSQL_ARG_INT
,
set_conflict
?
1
:
0
,
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_STRING
,
email
,
GPGSQL_ARG_STRING
,
conflict
?
conflict
:
""
,
GPGSQL_ARG_INT
,
(
int
)
effective_policy
,
GPGSQL_ARG_END
);
if
(
rc
)
{
log_error
(
_
(
"error updating TOFU database: %s
\n
"
),
err
);
print_further_info
(
" insert bindings <key: %s, user id: %s> = %s"
,
fingerprint
,
email
,
tofu_policy_str
(
policy
));
sqlite3_free
(
err
);
goto
leave
;
}
leave
:
xfree
(
fingerprint_pp
);
return
rc
;
}
/* Collect the strings returned by a query in a simple string list.
Any NULL values are converted to the empty string.
If a result has 3 rows and each row contains two columns, then the
results are added to the list as follows (the value is parentheses
is the 1-based index in the final list):
row 1, col 2 (6)
row 1, col 1 (5)
row 2, col 2 (4)
row 2, col 1 (3)
row 3, col 2 (2)
row 3, col 1 (1)
This is because add_to_strlist pushes the results onto the front of
the list. The end result is that the rows are backwards, but the
columns are in the expected order. */
static
int
strings_collect_cb
(
void
*
cookie
,
int
argc
,
char
**
argv
,
char
**
azColName
)
{
int
i
;
strlist_t
*
strlist
=
cookie
;
(
void
)
azColName
;
for
(
i
=
argc
-
1
;
i
>=
0
;
i
--
)
add_to_strlist
(
strlist
,
argv
[
i
]
?
argv
[
i
]
:
""
);
return
0
;
}
static
int
strings_collect_cb2
(
void
*
cookie
,
int
argc
,
char
**
argv
,
char
**
azColName
,
sqlite3_stmt
*
stmt
)
{
(
void
)
stmt
;
return
strings_collect_cb
(
cookie
,
argc
,
argv
,
azColName
);
}
/* Auxiliary data structure to collect statistics about
signatures. */
struct
signature_stats
{
struct
signature_stats
*
next
;
/* The user-assigned policy for this binding. */
enum
tofu_policy
policy
;
/* How long ago the signature was created (rounded to a multiple of
TIME_AGO_UNIT_SMALL, etc.). */
long
time_ago
;
/* Number of signatures during this time. */
unsigned
long
count
;
/* If the corresponding key/user id has been expired / revoked. */
int
is_expired
;
int
is_revoked
;
/* The key that generated this signature. */
char
fingerprint
[
1
];
};
static
void
signature_stats_free
(
struct
signature_stats
*
stats
)
{
while
(
stats
)
{
struct
signature_stats
*
next
=
stats
->
next
;
xfree
(
stats
);
stats
=
next
;
}
}
static
void
signature_stats_prepend
(
struct
signature_stats
**
statsp
,
const
char
*
fingerprint
,
enum
tofu_policy
policy
,
long
time_ago
,
unsigned
long
count
)
{
struct
signature_stats
*
stats
=
xmalloc_clear
(
sizeof
(
*
stats
)
+
strlen
(
fingerprint
));
stats
->
next
=
*
statsp
;
*
statsp
=
stats
;
strcpy
(
stats
->
fingerprint
,
fingerprint
);
stats
->
policy
=
policy
;
stats
->
time_ago
=
time_ago
;
stats
->
count
=
count
;
}
/* Process rows that contain the four columns:
<fingerprint, policy, time ago, count>. */
static
int
signature_stats_collect_cb
(
void
*
cookie
,
int
argc
,
char
**
argv
,
char
**
azColName
,
sqlite3_stmt
*
stmt
)
{
struct
signature_stats
**
statsp
=
cookie
;
int
i
=
0
;
enum
tofu_policy
policy
;
long
time_ago
;
unsigned
long
count
;
long
along
;
(
void
)
azColName
;
(
void
)
stmt
;
i
++
;
if
(
string_to_long
(
&
along
,
argv
[
i
],
0
,
__LINE__
))
return
1
;
/* Abort */
policy
=
along
;
i
++
;
if
(
!
argv
[
i
])
time_ago
=
0
;
else
{
if
(
string_to_long
(
&
time_ago
,
argv
[
i
],
0
,
__LINE__
))
return
1
;
/* Abort. */
}
i
++
;
/* If time_ago is NULL, then we had no messages, but we still have a
single row, which count(*) turns into 1. */
if
(
!
argv
[
i
-
1
])
count
=
0
;
else
{
if
(
string_to_ulong
(
&
count
,
argv
[
i
],
0
,
__LINE__
))
return
1
;
/* Abort */
}
i
++
;
log_assert
(
argc
==
i
);
signature_stats_prepend
(
statsp
,
argv
[
0
],
policy
,
time_ago
,
count
);
return
0
;
}
/* Format the first part of a conflict message and return that as a
* malloced string. Returns NULL on error. */
static
char
*
format_conflict_msg_part1
(
int
policy
,
strlist_t
conflict_set
,
const
char
*
email
)
{
estream_t
fp
;
char
*
fingerprint
;
char
*
tmpstr
,
*
text
;
log_assert
(
conflict_set
);
fingerprint
=
conflict_set
->
d
;
fp
=
es_fopenmem
(
0
,
"rw,samethread"
);
if
(
!
fp
)
log_fatal
(
"error creating memory stream: %s
\n
"
,
gpg_strerror
(
gpg_error_from_syserror
()));
if
(
policy
==
TOFU_POLICY_NONE
)
{
es_fprintf
(
fp
,
_
(
"This is the first time the email address
\"
%s
\"
is "
"being used with key %s."
),
email
,
fingerprint
);
es_fputs
(
" "
,
fp
);
}
else
if
(
policy
==
TOFU_POLICY_ASK
&&
conflict_set
->
next
)
{
int
conflicts
=
strlist_length
(
conflict_set
);
es_fprintf
(
fp
,
ngettext
(
"The email address
\"
%s
\"
is associated with %d key!"
,
"The email address
\"
%s
\"
is associated with %d keys!"
,
conflicts
),
email
,
conflicts
);
if
(
opt
.
verbose
)
es_fprintf
(
fp
,
_
(
" Since this binding's policy was 'auto', it has been "
"changed to 'ask'."
));
es_fputs
(
" "
,
fp
);
}
es_fprintf
(
fp
,
_
(
"Please indicate whether this email address should"
" be associated with key %s or whether you think someone"
" is impersonating
\"
%s
\"
."
),
fingerprint
,
email
);
es_fputc
(
'\n'
,
fp
);
es_fputc
(
0
,
fp
);
if
(
es_fclose_snatch
(
fp
,
(
void
**
)
&
tmpstr
,
NULL
))
log_fatal
(
"error snatching memory stream
\n
"
);
text
=
format_text
(
tmpstr
,
72
,
80
);
es_free
(
tmpstr
);
return
text
;
}
/* Return 1 if A signed B and B signed A. */
static
int
cross_sigs
(
const
char
*
email
,
kbnode_t
a
,
kbnode_t
b
)
{
int
i
;
PKT_public_key
*
a_pk
=
a
->
pkt
->
pkt
.
public_key
;
PKT_public_key
*
b_pk
=
b
->
pkt
->
pkt
.
public_key
;
char
a_keyid
[
33
];
char
b_keyid
[
33
];
if
(
DBG_TRUST
)
{
format_keyid
(
pk_main_keyid
(
a_pk
),
KF_LONG
,
a_keyid
,
sizeof
(
a_keyid
));
format_keyid
(
pk_main_keyid
(
b_pk
),
KF_LONG
,
b_keyid
,
sizeof
(
b_keyid
));
}
for
(
i
=
0
;
i
<
2
;
i
++
)
{
/* See if SIGNER signed SIGNEE. */
kbnode_t
signer
=
i
==
0
?
a
:
b
;
kbnode_t
signee
=
i
==
0
?
b
:
a
;
PKT_public_key
*
signer_pk
=
signer
->
pkt
->
pkt
.
public_key
;
u32
*
signer_kid
=
pk_main_keyid
(
signer_pk
);
kbnode_t
n
;
int
saw_email
=
0
;
/* Iterate over SIGNEE's keyblock and see if there is a valid
signature from SIGNER. */
for
(
n
=
signee
;
n
;
n
=
n
->
next
)
{
PKT_signature
*
sig
;
if
(
n
->
pkt
->
pkttype
==
PKT_USER_ID
)
{
if
(
saw_email
)
/* We're done: we've processed all signatures on the
user id. */
break
;
else
{
/* See if this is the matching user id. */
PKT_user_id
*
user_id
=
n
->
pkt
->
pkt
.
user_id
;
char
*
email2
=
email_from_user_id
(
user_id
->
name
);
if
(
strcmp
(
email
,
email2
)
==
0
)
saw_email
=
1
;
xfree
(
email2
);
}
}
if
(
!
saw_email
)
continue
;
if
(
n
->
pkt
->
pkttype
!=
PKT_SIGNATURE
)
continue
;
sig
=
n
->
pkt
->
pkt
.
signature
;
if
(
!
(
sig
->
sig_class
==
0x10
||
sig
->
sig_class
==
0x11
||
sig
->
sig_class
==
0x12
||
sig
->
sig_class
==
0x13
))
/* Not a signature over a user id. */
continue
;
/* SIG is on SIGNEE's keyblock. If SIG was generated by the
signer, then it's a match. */
if
(
keyid_cmp
(
sig
->
keyid
,
signer_kid
)
==
0
)
/* Match! */
break
;
}
if
(
!
n
)
/* We didn't find a signature from signer over signee. */
{
if
(
DBG_TRUST
)
log_debug
(
"No cross sig between %s and %s
\n
"
,
a_keyid
,
b_keyid
);
return
0
;
}
}
/* A signed B and B signed A. */
if
(
DBG_TRUST
)
log_debug
(
"Cross sig between %s and %s
\n
"
,
a_keyid
,
b_keyid
);
return
1
;
}
/* Return whether the key was signed by an ultimately trusted key. */
static
int
signed_by_utk
(
const
char
*
email
,
kbnode_t
a
)
{
kbnode_t
n
;
int
saw_email
=
0
;
for
(
n
=
a
;
n
;
n
=
n
->
next
)
{
PKT_signature
*
sig
;
if
(
n
->
pkt
->
pkttype
==
PKT_USER_ID
)
{
if
(
saw_email
)
/* We're done: we've processed all signatures on the
user id. */
break
;
else
{
/* See if this is the matching user id. */
PKT_user_id
*
user_id
=
n
->
pkt
->
pkt
.
user_id
;
char
*
email2
=
email_from_user_id
(
user_id
->
name
);
if
(
strcmp
(
email
,
email2
)
==
0
)
saw_email
=
1
;
xfree
(
email2
);
}
}
if
(
!
saw_email
)
continue
;
if
(
n
->
pkt
->
pkttype
!=
PKT_SIGNATURE
)
continue
;
sig
=
n
->
pkt
->
pkt
.
signature
;
if
(
!
(
sig
->
sig_class
==
0x10
||
sig
->
sig_class
==
0x11
||
sig
->
sig_class
==
0x12
||
sig
->
sig_class
==
0x13
))
/* Not a signature over a user id. */
continue
;
/* SIG is on SIGNEE's keyblock. If SIG was generated by the
signer, then it's a match. */
if
(
tdb_keyid_is_utk
(
sig
->
keyid
))
{
/* Match! */
if
(
DBG_TRUST
)
log_debug
(
"TOFU: %s is signed by an ultimately trusted key.
\n
"
,
pk_keyid_str
(
a
->
pkt
->
pkt
.
public_key
));
return
1
;
}
}
if
(
DBG_TRUST
)
log_debug
(
"TOFU: %s is NOT signed by an ultimately trusted key.
\n
"
,
pk_keyid_str
(
a
->
pkt
->
pkt
.
public_key
));
return
0
;
}
enum
{
BINDING_NEW
=
1
<<
0
,
BINDING_CONFLICT
=
1
<<
1
,
BINDING_EXPIRED
=
1
<<
2
,
BINDING_REVOKED
=
1
<<
3
};
/* Ask the user about the binding. There are three ways we could end
* up here:
*
* - This is a new binding and there is a conflict
* (policy == TOFU_POLICY_NONE && conflict_set_count > 1),
*
* - This is a new binding and opt.tofu_default_policy is set to
* ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
* TOFU_POLICY_ASK), or,
*
* - The policy is ask (the user deferred last time) (policy ==
* TOFU_POLICY_ASK).
*
* Note: this function must not be called while in a transaction!
*
* CONFLICT_SET includes all of the conflicting bindings
* with FINGERPRINT first. FLAGS is a bit-wise or of
* BINDING_NEW, etc.
*/
static
void
ask_about_binding
(
ctrl_t
ctrl
,
enum
tofu_policy
*
policy
,
int
*
trust_level
,
strlist_t
conflict_set
,
const
char
*
fingerprint
,
const
char
*
email
,
const
char
*
user_id
,
time_t
now
)
{
tofu_dbs_t
dbs
;
strlist_t
iter
;
int
conflict_set_count
=
strlist_length
(
conflict_set
);
char
*
sqerr
=
NULL
;
int
rc
;
estream_t
fp
;
strlist_t
other_user_ids
=
NULL
;
struct
signature_stats
*
stats
=
NULL
;
struct
signature_stats
*
stats_iter
=
NULL
;
char
*
prompt
=
NULL
;
const
char
*
choices
;
dbs
=
ctrl
->
tofu
.
dbs
;
log_assert
(
dbs
);
log_assert
(
dbs
->
in_transaction
==
0
);
fp
=
es_fopenmem
(
0
,
"rw,samethread"
);
if
(
!
fp
)
log_fatal
(
"error creating memory stream: %s
\n
"
,
gpg_strerror
(
gpg_error_from_syserror
()));
{
char
*
text
=
format_conflict_msg_part1
(
*
policy
,
conflict_set
,
email
);
if
(
!
text
)
/* FIXME: Return the error all the way up. */
log_fatal
(
"format failed: %s
\n
"
,
gpg_strerror
(
gpg_error_from_syserror
()));
es_fputs
(
text
,
fp
);
es_fputc
(
'\n'
,
fp
);
xfree
(
text
);
}
begin_transaction
(
ctrl
,
0
);
/* Find other user ids associated with this key and whether the
* bindings are marked as good or bad. */
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
get_trust_gather_other_user_ids
,
strings_collect_cb2
,
&
other_user_ids
,
&
sqerr
,
"select user_id, policy from bindings where fingerprint = ?;"
,
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_END
);
if
(
rc
)
{
log_error
(
_
(
"error gathering other user IDs: %s
\n
"
),
sqerr
);
sqlite3_free
(
sqerr
);
sqerr
=
NULL
;
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
}
if
(
other_user_ids
)
{
strlist_t
strlist_iter
;
es_fprintf
(
fp
,
_
(
"This key's user IDs:
\n
"
));
for
(
strlist_iter
=
other_user_ids
;
strlist_iter
;
strlist_iter
=
strlist_iter
->
next
)
{
char
*
other_user_id
=
strlist_iter
->
d
;
char
*
other_thing
;
enum
tofu_policy
other_policy
;
log_assert
(
strlist_iter
->
next
);
strlist_iter
=
strlist_iter
->
next
;
other_thing
=
strlist_iter
->
d
;
other_policy
=
atoi
(
other_thing
);
es_fprintf
(
fp
,
" %s ("
,
other_user_id
);
es_fprintf
(
fp
,
_
(
"policy: %s"
),
tofu_policy_str
(
other_policy
));
es_fprintf
(
fp
,
")
\n
"
);
}
es_fprintf
(
fp
,
"
\n
"
);
free_strlist
(
other_user_ids
);
}
/* Get the stats for all the keys in CONFLICT_SET. */
strlist_rev
(
&
conflict_set
);
for
(
iter
=
conflict_set
;
iter
&&
!
rc
;
iter
=
iter
->
next
)
{
#define STATS_SQL(table, time, sign) \
"select fingerprint, policy, time_ago, count(*)\n" \
" from\n" \
" (select bindings.*,\n" \
" "sign" case\n" \
" when delta ISNULL then 1\n" \
/* From the future (but if its just a couple of hours in the \
* future don't turn it into a warning)? Or should we use \
* small, medium or large units? (Note: whatever we do, we \
* keep the value in seconds. Then when we group, everything \
* that rounds to the same number of seconds is grouped.) */
\
" when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n" \
" when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n" \
" then 3\n" \
" when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n" \
" then 4\n" \
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n" \
" then 5\n" \
" else 6\n" \
" end time_ago,\n" \
" delta time_ago_raw\n" \
" from bindings\n" \
" left join\n" \
" (select *,\n" \
" cast(? - " time " as real) delta\n" \
" from " table ") ss\n" \
" on ss.binding = bindings.oid)\n" \
" where email = ? and fingerprint = ?\n" \
" group by time_ago\n" \
/* Make sure the current key is first. */
\
" order by time_ago desc;\n"
/* Use the time when we saw the signature, not when the
signature was created as that can be forged. */
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
get_trust_gather_signature_stats
,
signature_stats_collect_cb
,
&
stats
,
&
sqerr
,
STATS_SQL
(
"signatures"
,
"time"
,
""
),
GPGSQL_ARG_LONG_LONG
,
(
long
long
)
now
,
GPGSQL_ARG_STRING
,
email
,
GPGSQL_ARG_STRING
,
iter
->
d
,
GPGSQL_ARG_END
);
if
(
rc
)
{
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
break
;
}
if
(
!
stats
||
strcmp
(
iter
->
d
,
stats
->
fingerprint
)
!=
0
)
/* No stats for this binding. Add a dummy entry. */
signature_stats_prepend
(
&
stats
,
iter
->
d
,
TOFU_POLICY_AUTO
,
1
,
1
);
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
get_trust_gather_encryption_stats
,
signature_stats_collect_cb
,
&
stats
,
&
sqerr
,
STATS_SQL
(
"encryptions"
,
"time"
,
"-"
),
GPGSQL_ARG_LONG_LONG
,
(
long
long
)
now
,
GPGSQL_ARG_STRING
,
email
,
GPGSQL_ARG_STRING
,
iter
->
d
,
GPGSQL_ARG_END
);
if
(
rc
)
{
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
break
;
}
#undef STATS_SQL
if
(
!
stats
||
strcmp
(
iter
->
d
,
stats
->
fingerprint
)
!=
0
||
stats
->
time_ago
>
0
)
/* No stats for this binding. Add a dummy entry. */
signature_stats_prepend
(
&
stats
,
iter
->
d
,
TOFU_POLICY_AUTO
,
-1
,
1
);
}
end_transaction
(
ctrl
,
0
);
strlist_rev
(
&
conflict_set
);
if
(
rc
)
{
strlist_t
strlist_iter
;
log_error
(
_
(
"error gathering signature stats: %s
\n
"
),
sqerr
);
sqlite3_free
(
sqerr
);
sqerr
=
NULL
;
es_fprintf
(
fp
,
ngettext
(
"The email address
\"
%s
\"
is"
" associated with %d key:
\n
"
,
"The email address
\"
%s
\"
is"
" associated with %d keys:
\n
"
,
conflict_set_count
),
email
,
conflict_set_count
);
for
(
strlist_iter
=
conflict_set
;
strlist_iter
;
strlist_iter
=
strlist_iter
->
next
)
es_fprintf
(
fp
,
" %s
\n
"
,
strlist_iter
->
d
);
}
else
{
char
*
key
=
NULL
;
strlist_t
binding
;
int
seen_in_past
=
0
;
int
encrypted
=
1
;
es_fprintf
(
fp
,
_
(
"Statistics for keys"
" with the email address
\"
%s
\"
:
\n
"
),
email
);
for
(
stats_iter
=
stats
;
stats_iter
;
stats_iter
=
stats_iter
->
next
)
{
#if 0
log_debug ("%s: time_ago: %ld; count: %ld\n",
stats_iter->fingerprint,
stats_iter->time_ago,
stats_iter->count);
#endif
if
(
stats_iter
->
time_ago
>
0
&&
encrypted
)
{
/* We've change from the encrypted stats to the verified
* stats. Reset SEEN_IN_PAST. */
encrypted
=
0
;
seen_in_past
=
0
;
}
if
(
!
key
||
strcmp
(
key
,
stats_iter
->
fingerprint
))
{
int
this_key
;
char
*
key_pp
;
key
=
stats_iter
->
fingerprint
;
this_key
=
strcmp
(
key
,
fingerprint
)
==
0
;
key_pp
=
format_hexfingerprint
(
key
,
NULL
,
0
);
es_fprintf
(
fp
,
" %s ("
,
key_pp
);
/* Find the associated binding. */
for
(
binding
=
conflict_set
;
binding
;
binding
=
binding
->
next
)
if
(
strcmp
(
key
,
binding
->
d
)
==
0
)
break
;
log_assert
(
binding
);
if
((
binding
->
flags
&
BINDING_REVOKED
))
{
es_fprintf
(
fp
,
_
(
"revoked"
));
es_fprintf
(
fp
,
", "
);
}
else
if
((
binding
->
flags
&
BINDING_EXPIRED
))
{
es_fprintf
(
fp
,
_
(
"expired"
));
es_fprintf
(
fp
,
", "
);
}
if
(
this_key
)
es_fprintf
(
fp
,
_
(
"this key"
));
else
es_fprintf
(
fp
,
_
(
"policy: %s"
),
tofu_policy_str
(
stats_iter
->
policy
));
es_fputs
(
"):
\n
"
,
fp
);
xfree
(
key_pp
);
seen_in_past
=
0
;
show_statistics
(
dbs
,
stats_iter
->
fingerprint
,
email
,
TOFU_POLICY_ASK
,
NULL
,
1
,
now
);
}
if
(
labs
(
stats_iter
->
time_ago
)
==
1
)
{
/* The 1 in this case is the NULL entry. */
log_assert
(
stats_iter
->
count
==
1
);
stats_iter
->
count
=
0
;
}
seen_in_past
+=
stats_iter
->
count
;
es_fputs
(
" "
,
fp
);
if
(
!
stats_iter
->
count
)
{
if
(
stats_iter
->
time_ago
>
0
)
es_fprintf
(
fp
,
ngettext
(
"Verified %d message."
,
"Verified %d messages."
,
seen_in_past
),
seen_in_past
);
else
es_fprintf
(
fp
,
ngettext
(
"Encrypted %d message."
,
"Encrypted %d messages."
,
seen_in_past
),
seen_in_past
);
}
else
if
(
labs
(
stats_iter
->
time_ago
)
==
2
)
{
if
(
stats_iter
->
time_ago
>
0
)
es_fprintf
(
fp
,
ngettext
(
"Verified %d message in the future."
,
"Verified %d messages in the future."
,
seen_in_past
),
seen_in_past
);
else
es_fprintf
(
fp
,
ngettext
(
"Encrypted %d message in the future."
,
"Encrypted %d messages in the future."
,
seen_in_past
),
seen_in_past
);
/* Reset it. */
seen_in_past
=
0
;
}
else
{
if
(
labs
(
stats_iter
->
time_ago
)
==
3
)
{
int
days
=
1
+
stats_iter
->
time_ago
/
TIME_AGO_UNIT_SMALL
;
if
(
stats_iter
->
time_ago
>
0
)
es_fprintf
(
fp
,
ngettext
(
"Messages verified over the past %d day: %d."
,
"Messages verified over the past %d days: %d."
,
days
),
days
,
seen_in_past
);
else
es_fprintf
(
fp
,
ngettext
(
"Messages encrypted over the past %d day: %d."
,
"Messages encrypted over the past %d days: %d."
,
days
),
days
,
seen_in_past
);
}
else
if
(
labs
(
stats_iter
->
time_ago
)
==
4
)
{
int
months
=
1
+
stats_iter
->
time_ago
/
TIME_AGO_UNIT_MEDIUM
;
if
(
stats_iter
->
time_ago
>
0
)
es_fprintf
(
fp
,
ngettext
(
"Messages verified over the past %d month: %d."
,
"Messages verified over the past %d months: %d."
,
months
),
months
,
seen_in_past
);
else
es_fprintf
(
fp
,
ngettext
(
"Messages encrypted over the past %d month: %d."
,
"Messages encrypted over the past %d months: %d."
,
months
),
months
,
seen_in_past
);
}
else
if
(
labs
(
stats_iter
->
time_ago
)
==
5
)
{
int
years
=
1
+
stats_iter
->
time_ago
/
TIME_AGO_UNIT_LARGE
;
if
(
stats_iter
->
time_ago
>
0
)
es_fprintf
(
fp
,
ngettext
(
"Messages verified over the past %d year: %d."
,
"Messages verified over the past %d years: %d."
,
years
),
years
,
seen_in_past
);
else
es_fprintf
(
fp
,
ngettext
(
"Messages encrypted over the past %d year: %d."
,
"Messages encrypted over the past %d years: %d."
,
years
),
years
,
seen_in_past
);
}
else
if
(
labs
(
stats_iter
->
time_ago
)
==
6
)
{
if
(
stats_iter
->
time_ago
>
0
)
es_fprintf
(
fp
,
_
(
"Messages verified in the past: %d."
),
seen_in_past
);
else
es_fprintf
(
fp
,
_
(
"Messages encrypted in the past: %d."
),
seen_in_past
);
}
else
log_assert
(
!
"Broken SQL.
\n
"
);
}
es_fputs
(
"
\n
"
,
fp
);
}
}
if
(
conflict_set_count
>
1
||
(
conflict_set
->
flags
&
BINDING_CONFLICT
))
{
/* This is a conflict. */
/* TRANSLATORS: Please translate the text found in the source
* file below. We don't directly internationalize that text so
* that we can tweak it without breaking translations. */
const
char
*
text
=
_
(
"TOFU detected a binding conflict"
);
char
*
textbuf
;
if
(
!
strcmp
(
text
,
"TOFU detected a binding conflict"
))
{
/* No translation. Use the English text. */
text
=
"Normally, an email address is associated with a single key. "
"However, people sometimes generate a new key if "
"their key is too old or they think it might be compromised. "
"Alternatively, a new key may indicate a man-in-the-middle "
"attack! Before accepting this association, you should talk to or "
"call the person to make sure this new key is legitimate."
;
}
textbuf
=
format_text
(
text
,
72
,
80
);
es_fprintf
(
fp
,
"
\n
%s
\n
"
,
textbuf
?
textbuf
:
"[OUT OF CORE!]"
);
xfree
(
textbuf
);
}
es_fputc
(
'\n'
,
fp
);
/* Add a NUL terminator. */
es_fputc
(
0
,
fp
);
if
(
es_fclose_snatch
(
fp
,
(
void
**
)
&
prompt
,
NULL
))
log_fatal
(
"error snatching memory stream
\n
"
);
/* I think showing the large message once is sufficient. If we
* would move it right before the cpr_get many lines will scroll
* away and the user might not realize that he merely entered a
* wrong choice (because he does not see that either). As a small
* benefit we allow C-L to redisplay everything. */
tty_printf
(
"%s"
,
prompt
);
/* Suspend any transaction: it could take a while until the user
responds. */
tofu_suspend_batch_transaction
(
ctrl
);
while
(
1
)
{
char
*
response
;
/* TRANSLATORS: Two letters (normally the lower and upper case
* version of the hotkey) for each of the five choices. If
* there is only one choice in your language, repeat it. */
choices
=
_
(
"gG"
"aA"
"uU"
"rR"
"bB"
);
if
(
strlen
(
choices
)
!=
10
)
log_bug
(
"Bad TOFU conflict translation! Please report."
);
response
=
cpr_get
(
"tofu.conflict"
,
_
(
"(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? "
));
trim_spaces
(
response
);
cpr_kill_prompt
();
if
(
*
response
==
CONTROL_L
)
tty_printf
(
"%s"
,
prompt
);
else
if
(
!
response
[
0
])
/* Default to unknown. Don't save it. */
{
tty_printf
(
_
(
"Defaulting to unknown.
\n
"
));
*
policy
=
TOFU_POLICY_UNKNOWN
;
break
;
}
else
if
(
!
response
[
1
])
{
char
*
choice
=
strchr
(
choices
,
*
response
);
if
(
choice
)
{
int
c
=
((
size_t
)
choice
-
(
size_t
)
choices
)
/
2
;
switch
(
c
)
{
case
0
:
/* Good. */
*
policy
=
TOFU_POLICY_GOOD
;
*
trust_level
=
tofu_policy_to_trust_level
(
*
policy
);
break
;
case
1
:
/* Accept once. */
*
policy
=
TOFU_POLICY_ASK
;
*
trust_level
=
tofu_policy_to_trust_level
(
TOFU_POLICY_GOOD
);
break
;
case
2
:
/* Unknown. */
*
policy
=
TOFU_POLICY_UNKNOWN
;
*
trust_level
=
tofu_policy_to_trust_level
(
*
policy
);
break
;
case
3
:
/* Reject once. */
*
policy
=
TOFU_POLICY_ASK
;
*
trust_level
=
tofu_policy_to_trust_level
(
TOFU_POLICY_BAD
);
break
;
case
4
:
/* Bad. */
*
policy
=
TOFU_POLICY_BAD
;
*
trust_level
=
tofu_policy_to_trust_level
(
*
policy
);
break
;
default
:
log_bug
(
"c should be between 0 and 4 but it is %d!"
,
c
);
}
if
(
record_binding
(
dbs
,
fingerprint
,
email
,
user_id
,
*
policy
,
TOFU_POLICY_NONE
,
NULL
,
0
,
0
,
now
))
{
/* If there's an error registering the
* binding, don't save the signature. */
*
trust_level
=
_tofu_GET_TRUST_ERROR
;
}
break
;
}
}
xfree
(
response
);
}
tofu_resume_batch_transaction
(
ctrl
);
xfree
(
prompt
);
signature_stats_free
(
stats
);
}
/* Return the set of keys that conflict with the binding <fingerprint,
email> (including the binding itself, which will be first in the
list). For each returned key also sets BINDING_NEW, etc. */
static
strlist_t
build_conflict_set
(
ctrl_t
ctrl
,
tofu_dbs_t
dbs
,
PKT_public_key
*
pk
,
const
char
*
fingerprint
,
const
char
*
email
)
{
gpg_error_t
rc
;
char
*
sqerr
;
strlist_t
conflict_set
=
NULL
;
int
conflict_set_count
;
strlist_t
iter
;
kbnode_t
*
kb_all
;
KEYDB_HANDLE
hd
;
int
i
;
/* Get the fingerprints of any bindings that share the email address
* and whether the bindings have a known conflict.
*
* Note: if the binding in question is in the DB, it will also be
* returned. Thus, if the result set is empty, then <email,
* fingerprint> is a new binding. */
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
get_trust_bindings_with_this_email
,
strings_collect_cb2
,
&
conflict_set
,
&
sqerr
,
"select"
/* A binding should only appear once, but try not to break in the
* case of corruption. */
" fingerprint || case sum(conflict NOTNULL) when 0 then '' else '!' end"
" from bindings where email = ?"
" group by fingerprint"
/* Make sure the current key comes first in the result list (if
it is present). */
" order by fingerprint = ? asc, fingerprint desc;"
,
GPGSQL_ARG_STRING
,
email
,
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_END
);
if
(
rc
)
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
sqerr
);
print_further_info
(
"listing fingerprints"
);
sqlite3_free
(
sqerr
);
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
return
NULL
;
}
/* Set BINDING_CONFLICT if the binding has a known conflict. This
* allows us to distinguish between bindings where the user
* explicitly set the policy to ask and bindings where we set the
* policy to ask due to a conflict. */
for
(
iter
=
conflict_set
;
iter
;
iter
=
iter
->
next
)
{
int
l
=
strlen
(
iter
->
d
);
if
(
!
(
l
==
2
*
MAX_FINGERPRINT_LEN
||
l
==
2
*
MAX_FINGERPRINT_LEN
+
1
))
{
log_error
(
_
(
"TOFU db corruption detected.
\n
"
));
print_further_info
(
"fingerprint '%s' is not %d characters long"
,
iter
->
d
,
2
*
MAX_FINGERPRINT_LEN
);
}
if
(
l
>=
1
&&
iter
->
d
[
l
-
1
]
==
'!'
)
{
iter
->
flags
|=
BINDING_CONFLICT
;
/* Remove the !. */
iter
->
d
[
l
-
1
]
=
0
;
}
}
/* If the current binding has not yet been recorded, add it to the
* list. (The order by above ensures that if it is present, it will
* be first.) */
if
(
!
(
conflict_set
&&
strcmp
(
conflict_set
->
d
,
fingerprint
)
==
0
))
{
add_to_strlist
(
&
conflict_set
,
fingerprint
);
conflict_set
->
flags
|=
BINDING_NEW
;
}
conflict_set_count
=
strlist_length
(
conflict_set
);
/* Eliminate false conflicts. */
if
(
conflict_set_count
==
1
)
/* We only have a single key. There are no false conflicts to
eliminate. But, we do need to set the flags. */
{
if
(
pk
->
has_expired
)
conflict_set
->
flags
|=
BINDING_EXPIRED
;
if
(
pk
->
flags
.
revoked
)
conflict_set
->
flags
|=
BINDING_REVOKED
;
return
conflict_set
;
}
/* If two keys have cross signatures, then they are controlled by
* the same person and thus are not in conflict. */
kb_all
=
xcalloc
(
sizeof
(
kb_all
[
0
]),
conflict_set_count
);
hd
=
keydb_new
();
for
(
i
=
0
,
iter
=
conflict_set
;
i
<
conflict_set_count
;
i
++
,
iter
=
iter
->
next
)
{
char
*
fp
=
iter
->
d
;
KEYDB_SEARCH_DESC
desc
;
kbnode_t
kb
;
PKT_public_key
*
binding_pk
;
kbnode_t
n
;
int
found_user_id
;
rc
=
keydb_search_reset
(
hd
);
if
(
rc
)
{
log_error
(
"resetting keydb failed: %s
\n
"
,
gpg_strerror
(
rc
));
continue
;
}
rc
=
classify_user_id
(
fp
,
&
desc
,
0
);
if
(
rc
)
{
log_error
(
_
(
"error parsing key specification '%s': %s
\n
"
),
fp
,
gpg_strerror
(
rc
));
continue
;
}
rc
=
keydb_search
(
hd
,
&
desc
,
1
,
NULL
);
if
(
rc
)
{
/* Note: it is entirely possible that we don't have the key
corresponding to an entry in the TOFU DB. This can
happen if we merge two TOFU DBs, but not the key
rings. */
log_info
(
_
(
"key
\"
%s
\"
not found: %s
\n
"
),
fp
,
gpg_strerror
(
rc
));
continue
;
}
rc
=
keydb_get_keyblock
(
hd
,
&
kb
);
if
(
rc
)
{
log_error
(
_
(
"error reading keyblock: %s
\n
"
),
gpg_strerror
(
rc
));
print_further_info
(
"fingerprint: %s"
,
fp
);
continue
;
}
merge_keys_and_selfsig
(
ctrl
,
kb
);
log_assert
(
kb
->
pkt
->
pkttype
==
PKT_PUBLIC_KEY
);
kb_all
[
i
]
=
kb
;
/* Since we have the key block, use this opportunity to figure
* out if the binding is expired or revoked. */
binding_pk
=
kb
->
pkt
->
pkt
.
public_key
;
/* The binding is always expired/revoked if the key is
* expired/revoked. */
if
(
binding_pk
->
has_expired
)
iter
->
flags
|=
BINDING_EXPIRED
;
if
(
binding_pk
->
flags
.
revoked
)
iter
->
flags
|=
BINDING_REVOKED
;
/* The binding is also expired/revoked if the user id is
* expired/revoked. */
n
=
kb
;
found_user_id
=
0
;
while
((
n
=
find_next_kbnode
(
n
,
PKT_USER_ID
))
&&
!
found_user_id
)
{
PKT_user_id
*
user_id2
=
n
->
pkt
->
pkt
.
user_id
;
char
*
email2
;
if
(
user_id2
->
attrib_data
)
continue
;
email2
=
email_from_user_id
(
user_id2
->
name
);
if
(
strcmp
(
email
,
email2
)
==
0
)
{
found_user_id
=
1
;
if
(
user_id2
->
flags
.
revoked
)
iter
->
flags
|=
BINDING_REVOKED
;
if
(
user_id2
->
flags
.
expired
)
iter
->
flags
|=
BINDING_EXPIRED
;
}
xfree
(
email2
);
}
if
(
!
found_user_id
)
{
log_info
(
_
(
"TOFU db corruption detected.
\n
"
));
print_further_info
(
"user id '%s' not on key block '%s'"
,
email
,
fingerprint
);
}
}
keydb_release
(
hd
);
/* Now that we have the key blocks, check for cross sigs. */
{
int
j
;
strlist_t
*
prevp
;
strlist_t
iter_next
;
int
*
die
;
log_assert
(
conflict_set_count
>
0
);
die
=
xtrycalloc
(
conflict_set_count
,
sizeof
*
die
);
if
(
!
die
)
{
/*err = gpg_error_from_syserror ();*/
xoutofcore
();
/* Fixme: Let the function return an error. */
}
for
(
i
=
0
;
i
<
conflict_set_count
;
i
++
)
{
/* Look for cross sigs between this key (i == 0) or a key
* that has cross sigs with i == 0 (i.e., transitively) */
if
(
!
(
i
==
0
||
die
[
i
]))
continue
;
for
(
j
=
i
+
1
;
j
<
conflict_set_count
;
j
++
)
/* Be careful: we might not have a key block for a key. */
if
(
kb_all
[
i
]
&&
kb_all
[
j
]
&&
cross_sigs
(
email
,
kb_all
[
i
],
kb_all
[
j
]))
die
[
j
]
=
1
;
}
/* Free unconflicting bindings (and all of the key blocks). */
for
(
iter
=
conflict_set
,
prevp
=
&
conflict_set
,
i
=
0
;
iter
;
iter
=
iter_next
,
i
++
)
{
iter_next
=
iter
->
next
;
release_kbnode
(
kb_all
[
i
]);
if
(
die
[
i
])
{
*
prevp
=
iter_next
;
iter
->
next
=
NULL
;
free_strlist
(
iter
);
conflict_set_count
--
;
}
else
{
prevp
=
&
iter
->
next
;
}
}
/* We shouldn't have removed the head. */
log_assert
(
conflict_set
);
log_assert
(
conflict_set_count
>=
1
);
xfree
(
die
);
}
xfree
(
kb_all
);
if
(
DBG_TRUST
)
{
log_debug
(
"binding <key: %s, email: %s> conflicts:
\n
"
,
fingerprint
,
email
);
for
(
iter
=
conflict_set
;
iter
;
iter
=
iter
->
next
)
{
log_debug
(
" %s:%s%s%s%s
\n
"
,
iter
->
d
,
(
iter
->
flags
&
BINDING_NEW
)
?
" new"
:
""
,
(
iter
->
flags
&
BINDING_CONFLICT
)
?
" known_conflict"
:
""
,
(
iter
->
flags
&
BINDING_EXPIRED
)
?
" expired"
:
""
,
(
iter
->
flags
&
BINDING_REVOKED
)
?
" revoked"
:
""
);
}
}
return
conflict_set
;
}
/* Return the effective policy for the binding <FINGERPRINT, EMAIL>
* (email has already been normalized). Returns
* _tofu_GET_POLICY_ERROR if an error occurs. Returns any conflict
* information in *CONFLICT_SETP if CONFLICT_SETP is not NULL and the
* returned policy is TOFU_POLICY_ASK (consequently, if there is a
* conflict, but the user set the policy to good *CONFLICT_SETP will
* empty). Note: as per build_conflict_set, which is used to build
* the conflict information, the conflict information includes the
* current user id as the first element of the linked list.
*
* This function registers the binding in the bindings table if it has
* not yet been registered.
*/
static
enum
tofu_policy
get_policy
(
ctrl_t
ctrl
,
tofu_dbs_t
dbs
,
PKT_public_key
*
pk
,
const
char
*
fingerprint
,
const
char
*
user_id
,
const
char
*
email
,
strlist_t
*
conflict_setp
,
time_t
now
)
{
int
rc
;
char
*
err
=
NULL
;
strlist_t
results
=
NULL
;
enum
tofu_policy
policy
=
_tofu_GET_POLICY_ERROR
;
enum
tofu_policy
effective_policy_orig
=
TOFU_POLICY_NONE
;
enum
tofu_policy
effective_policy
=
_tofu_GET_POLICY_ERROR
;
long
along
;
char
*
conflict_orig
=
NULL
;
char
*
conflict
=
NULL
;
strlist_t
conflict_set
=
NULL
;
int
conflict_set_count
;
/* Check if the <FINGERPRINT, EMAIL> binding is known
(TOFU_POLICY_NONE cannot appear in the DB. Thus, if POLICY is
still TOFU_POLICY_NONE after executing the query, then the
result set was empty.) */
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
get_policy_select_policy_and_conflict
,
strings_collect_cb2
,
&
results
,
&
err
,
"select policy, conflict, effective_policy from bindings
\n
"
" where fingerprint = ? and email = ?"
,
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_STRING
,
email
,
GPGSQL_ARG_END
);
if
(
rc
)
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
err
);
print_further_info
(
"reading the policy"
);
sqlite3_free
(
err
);
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
goto
out
;
}
if
(
strlist_length
(
results
)
==
0
)
{
/* No results. Use the defaults. */
policy
=
TOFU_POLICY_NONE
;
effective_policy
=
TOFU_POLICY_NONE
;
}
else
if
(
strlist_length
(
results
)
==
3
)
{
/* Parse and sanity check the results. */
if
(
string_to_long
(
&
along
,
results
->
d
,
0
,
__LINE__
))
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
gpg_strerror
(
GPG_ERR_BAD_DATA
));
print_further_info
(
"bad value for policy: %s"
,
results
->
d
);
goto
out
;
}
policy
=
along
;
if
(
!
(
policy
==
TOFU_POLICY_AUTO
||
policy
==
TOFU_POLICY_GOOD
||
policy
==
TOFU_POLICY_UNKNOWN
||
policy
==
TOFU_POLICY_BAD
||
policy
==
TOFU_POLICY_ASK
))
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
gpg_strerror
(
GPG_ERR_DB_CORRUPTED
));
print_further_info
(
"invalid value for policy (%d)"
,
policy
);
effective_policy
=
_tofu_GET_POLICY_ERROR
;
goto
out
;
}
if
(
*
results
->
next
->
d
)
conflict
=
xstrdup
(
results
->
next
->
d
);
if
(
string_to_long
(
&
along
,
results
->
next
->
next
->
d
,
0
,
__LINE__
))
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
gpg_strerror
(
GPG_ERR_BAD_DATA
));
print_further_info
(
"bad value for effective policy: %s"
,
results
->
next
->
next
->
d
);
goto
out
;
}
effective_policy
=
along
;
if
(
!
(
effective_policy
==
TOFU_POLICY_NONE
||
effective_policy
==
TOFU_POLICY_AUTO
||
effective_policy
==
TOFU_POLICY_GOOD
||
effective_policy
==
TOFU_POLICY_UNKNOWN
||
effective_policy
==
TOFU_POLICY_BAD
||
effective_policy
==
TOFU_POLICY_ASK
))
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
gpg_strerror
(
GPG_ERR_DB_CORRUPTED
));
print_further_info
(
"invalid value for effective_policy (%d)"
,
effective_policy
);
effective_policy
=
_tofu_GET_POLICY_ERROR
;
goto
out
;
}
}
else
{
/* The result has the wrong form. */
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
gpg_strerror
(
GPG_ERR_BAD_DATA
));
print_further_info
(
"reading policy: expected 3 columns, got %d
\n
"
,
strlist_length
(
results
));
goto
out
;
}
/* Save the effective policy and conflict so we know if we changed
* them. */
effective_policy_orig
=
effective_policy
;
conflict_orig
=
conflict
;
/* Unless there is a conflict, if the effective policy is cached,
* just return it. The reason we don't do this when there is a
* conflict is because of the following scenario: assume A and B
* conflict and B has signed A's key. Now, later we import A's
* signature on B. We need to recheck A, but the signature was on
* B, i.e., when B changes, we invalidate B's effective policy, but
* we also need to invalidate A's effective policy. Instead, we
* assume that conflicts are rare and don't optimize for them, which
* would complicate the code. */
if
(
effective_policy
!=
TOFU_POLICY_NONE
&&
!
conflict
)
goto
out
;
/* If the user explicitly set the policy, then respect that. */
if
(
policy
!=
TOFU_POLICY_AUTO
&&
policy
!=
TOFU_POLICY_NONE
)
{
effective_policy
=
policy
;
goto
out
;
}
/* Unless proven wrong, assume the effective policy is 'auto'. */
effective_policy
=
TOFU_POLICY_AUTO
;
/* See if the key is ultimately trusted. */
{
u32
kid
[
2
];
keyid_from_pk
(
pk
,
kid
);
if
(
tdb_keyid_is_utk
(
kid
))
{
effective_policy
=
TOFU_POLICY_GOOD
;
goto
out
;
}
}
/* See if the key is signed by an ultimately trusted key. */
{
int
fingerprint_raw_len
=
strlen
(
fingerprint
)
/
2
;
char
fingerprint_raw
[
20
];
int
len
=
0
;
if
(
fingerprint_raw_len
!=
sizeof
fingerprint_raw
||
((
len
=
hex2bin
(
fingerprint
,
fingerprint_raw
,
fingerprint_raw_len
))
!=
strlen
(
fingerprint
)))
{
if
(
DBG_TRUST
)
log_debug
(
"TOFU: Bad fingerprint: %s (len: %zu, parsed: %d)
\n
"
,
fingerprint
,
strlen
(
fingerprint
),
len
);
}
else
{
int
lookup_err
;
kbnode_t
kb
;
lookup_err
=
get_pubkey_byfprint
(
ctrl
,
NULL
,
&
kb
,
fingerprint_raw
,
fingerprint_raw_len
);
if
(
lookup_err
)
{
if
(
DBG_TRUST
)
log_debug
(
"TOFU: Looking up %s: %s
\n
"
,
fingerprint
,
gpg_strerror
(
lookup_err
));
}
else
{
int
is_signed_by_utk
=
signed_by_utk
(
email
,
kb
);
release_kbnode
(
kb
);
if
(
is_signed_by_utk
)
{
effective_policy
=
TOFU_POLICY_GOOD
;
goto
out
;
}
}
}
}
/* Check for any conflicts / see if a previously discovered conflict
* disappeared. The latter can happen if the conflicting bindings
* are now cross signed, for instance. */
conflict_set
=
build_conflict_set
(
ctrl
,
dbs
,
pk
,
fingerprint
,
email
);
conflict_set_count
=
strlist_length
(
conflict_set
);
if
(
conflict_set_count
==
0
)
{
/* build_conflict_set should always at least return the current
binding. Something went wrong. */
effective_policy
=
_tofu_GET_POLICY_ERROR
;
goto
out
;
}
if
(
conflict_set_count
==
1
&&
(
conflict_set
->
flags
&
BINDING_NEW
))
{
/* We've never observed a binding with this email address and we
* have a default policy, which is not to ask the user. */
/* If we've seen this binding, then we've seen this email and
* policy couldn't possibly be TOFU_POLICY_NONE. */
log_assert
(
policy
==
TOFU_POLICY_NONE
);
if
(
DBG_TRUST
)
log_debug
(
"TOFU: New binding <key: %s, user id: %s>, no conflict.
\n
"
,
fingerprint
,
email
);
effective_policy
=
TOFU_POLICY_AUTO
;
goto
out
;
}
if
(
conflict_set_count
==
1
&&
(
conflict_set
->
flags
&
BINDING_CONFLICT
))
{
/* No known conflicts now, but there was a conflict. This means
* at some point, there was a conflict and we changed this
* binding's policy to ask and set the conflicting key. The
* conflict can go away if there is not a cross sig between the
* two keys. In this case, just silently clear the conflict and
* reset the policy to auto. */
if
(
DBG_TRUST
)
log_debug
(
"TOFU: binding <key: %s, user id: %s> had a conflict, but it's been resolved (probably via cross sig).
\n
"
,
fingerprint
,
email
);
effective_policy
=
TOFU_POLICY_AUTO
;
conflict
=
NULL
;
goto
out
;
}
if
(
conflict_set_count
==
1
)
{
/* No conflicts and never marked as conflicting. */
log_assert
(
!
conflict
);
effective_policy
=
TOFU_POLICY_AUTO
;
goto
out
;
}
/* There is a conflicting key. */
log_assert
(
conflict_set_count
>
1
);
effective_policy
=
TOFU_POLICY_ASK
;
conflict
=
xstrdup
(
conflict_set
->
next
->
d
);
out
:
log_assert
(
policy
==
_tofu_GET_POLICY_ERROR
||
policy
==
TOFU_POLICY_NONE
||
policy
==
TOFU_POLICY_AUTO
||
policy
==
TOFU_POLICY_GOOD
||
policy
==
TOFU_POLICY_UNKNOWN
||
policy
==
TOFU_POLICY_BAD
||
policy
==
TOFU_POLICY_ASK
);
/* Everything but NONE. */
log_assert
(
effective_policy
==
_tofu_GET_POLICY_ERROR
||
effective_policy
==
TOFU_POLICY_AUTO
||
effective_policy
==
TOFU_POLICY_GOOD
||
effective_policy
==
TOFU_POLICY_UNKNOWN
||
effective_policy
==
TOFU_POLICY_BAD
||
effective_policy
==
TOFU_POLICY_ASK
);
if
(
effective_policy
!=
TOFU_POLICY_ASK
&&
conflict
)
conflict
=
NULL
;
/* If we don't have a record of this binding, its effective policy
* changed, or conflict changed, update the DB. */
if
(
effective_policy
!=
_tofu_GET_POLICY_ERROR
&&
(
/* New binding. */
policy
==
TOFU_POLICY_NONE
/* effective_policy changed. */
||
effective_policy
!=
effective_policy_orig
/* conflict changed. */
||
(
conflict
!=
conflict_orig
&&
(
!
conflict
||
!
conflict_orig
||
strcmp
(
conflict
,
conflict_orig
)
!=
0
))))
{
if
(
record_binding
(
dbs
,
fingerprint
,
email
,
user_id
,
policy
==
TOFU_POLICY_NONE
?
TOFU_POLICY_AUTO
:
policy
,
effective_policy
,
conflict
,
1
,
0
,
now
)
!=
0
)
log_error
(
"error setting TOFU binding's policy"
" to %s
\n
"
,
tofu_policy_str
(
policy
));
}
/* If the caller wants the set of conflicts, return it. */
if
(
effective_policy
==
TOFU_POLICY_ASK
&&
conflict_setp
)
{
if
(
!
conflict_set
)
conflict_set
=
build_conflict_set
(
ctrl
,
dbs
,
pk
,
fingerprint
,
email
);
*
conflict_setp
=
conflict_set
;
}
else
{
free_strlist
(
conflict_set
);
if
(
conflict_setp
)
*
conflict_setp
=
NULL
;
}
xfree
(
conflict_orig
);
if
(
conflict
!=
conflict_orig
)
xfree
(
conflict
);
free_strlist
(
results
);
return
effective_policy
;
}
/* Return the trust level (TRUST_NEVER, etc.) for the binding
* <FINGERPRINT, EMAIL> (email is already normalized). If no policy
* is registered, returns TOFU_POLICY_NONE. If an error occurs,
* returns _tofu_GET_TRUST_ERROR.
*
* PK is the public key object for FINGERPRINT.
*
* USER_ID is the unadulterated user id.
*
* If MAY_ASK is set, then we may interact with the user. This is
* necessary if there is a conflict or the binding's policy is
* TOFU_POLICY_ASK. In the case of a conflict, we set the new
* conflicting binding's policy to TOFU_POLICY_ASK. In either case,
* we return TRUST_UNDEFINED. Note: if MAY_ASK is set, then this
* function must not be called while in a transaction! */
static
enum
tofu_policy
get_trust
(
ctrl_t
ctrl
,
PKT_public_key
*
pk
,
const
char
*
fingerprint
,
const
char
*
email
,
const
char
*
user_id
,
int
may_ask
,
enum
tofu_policy
*
policyp
,
strlist_t
*
conflict_setp
,
time_t
now
)
{
tofu_dbs_t
dbs
=
ctrl
->
tofu
.
dbs
;
int
in_transaction
=
0
;
enum
tofu_policy
policy
;
int
rc
;
char
*
sqerr
=
NULL
;
strlist_t
conflict_set
=
NULL
;
int
trust_level
=
TRUST_UNKNOWN
;
strlist_t
iter
;
log_assert
(
dbs
);
if
(
may_ask
)
log_assert
(
dbs
->
in_transaction
==
0
);
if
(
opt
.
batch
)
may_ask
=
0
;
log_assert
(
pk_is_primary
(
pk
));
/* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust
levels. */
log_assert
(
_tofu_GET_TRUST_ERROR
!=
TRUST_UNKNOWN
&&
_tofu_GET_TRUST_ERROR
!=
TRUST_EXPIRED
&&
_tofu_GET_TRUST_ERROR
!=
TRUST_UNDEFINED
&&
_tofu_GET_TRUST_ERROR
!=
TRUST_NEVER
&&
_tofu_GET_TRUST_ERROR
!=
TRUST_MARGINAL
&&
_tofu_GET_TRUST_ERROR
!=
TRUST_FULLY
&&
_tofu_GET_TRUST_ERROR
!=
TRUST_ULTIMATE
);
begin_transaction
(
ctrl
,
0
);
in_transaction
=
1
;
/* We need to call get_policy even if the key is ultimately trusted
* to make sure the binding has been registered. */
policy
=
get_policy
(
ctrl
,
dbs
,
pk
,
fingerprint
,
user_id
,
email
,
&
conflict_set
,
now
);
if
(
policy
==
TOFU_POLICY_ASK
)
/* The conflict set should always contain at least one element:
* the current key. */
log_assert
(
conflict_set
);
else
/* If the policy is not TOFU_POLICY_ASK, then conflict_set will be
* NULL. */
log_assert
(
!
conflict_set
);
/* If the key is ultimately trusted, there is nothing to do. */
{
u32
kid
[
2
];
keyid_from_pk
(
pk
,
kid
);
if
(
tdb_keyid_is_utk
(
kid
))
{
trust_level
=
TRUST_ULTIMATE
;
policy
=
TOFU_POLICY_GOOD
;
goto
out
;
}
}
if
(
policy
==
TOFU_POLICY_AUTO
)
{
policy
=
opt
.
tofu_default_policy
;
if
(
DBG_TRUST
)
log_debug
(
"TOFU: binding <key: %s, user id: %s>'s policy is"
" auto (default: %s).
\n
"
,
fingerprint
,
email
,
tofu_policy_str
(
opt
.
tofu_default_policy
));
if
(
policy
==
TOFU_POLICY_ASK
)
/* The default policy is ASK, but there is no conflict (policy
* was 'auto'). In this case, we need to make sure the
* conflict set includes at least the current user id. */
{
add_to_strlist
(
&
conflict_set
,
fingerprint
);
}
}
switch
(
policy
)
{
case
TOFU_POLICY_AUTO
:
case
TOFU_POLICY_GOOD
:
case
TOFU_POLICY_UNKNOWN
:
case
TOFU_POLICY_BAD
:
/* The saved judgement is auto -> auto, good, unknown or bad.
* We don't need to ask the user anything. */
if
(
DBG_TRUST
)
log_debug
(
"TOFU: Known binding <key: %s, user id: %s>'s policy: %s
\n
"
,
fingerprint
,
email
,
tofu_policy_str
(
policy
));
trust_level
=
tofu_policy_to_trust_level
(
policy
);
goto
out
;
case
TOFU_POLICY_ASK
:
/* We need to ask the user what to do. */
break
;
case
_tofu_GET_POLICY_ERROR
:
trust_level
=
_tofu_GET_TRUST_ERROR
;
goto
out
;
default
:
log_bug
(
"%s: Impossible value for policy (%d)
\n
"
,
__func__
,
policy
);
}
/* We get here if:
*
* 1. The saved policy is auto and the default policy is ask
* (get_policy() == TOFU_POLICY_AUTO
* && opt.tofu_default_policy == TOFU_POLICY_ASK)
*
* 2. The saved policy is ask (either last time the user selected
* accept once or reject once or there was a conflict and this
* binding's policy was changed from auto to ask)
* (policy == TOFU_POLICY_ASK).
*/
log_assert
(
policy
==
TOFU_POLICY_ASK
);
if
(
may_ask
)
{
/* We can't be in a normal transaction in ask_about_binding. */
end_transaction
(
ctrl
,
0
);
in_transaction
=
0
;
/* If we get here, we need to ask the user about the binding. */
ask_about_binding
(
ctrl
,
&
policy
,
&
trust_level
,
conflict_set
,
fingerprint
,
email
,
user_id
,
now
);
}
else
{
trust_level
=
TRUST_UNDEFINED
;
}
/* Mark any conflicting bindings that have an automatic policy as
* now requiring confirmation. Note: we do this after we ask for
* confirmation so that when the current policy is printed, it is
* correct. */
if
(
!
in_transaction
)
{
begin_transaction
(
ctrl
,
0
);
in_transaction
=
1
;
}
/* The conflict set should always contain at least one element:
* the current key. */
log_assert
(
conflict_set
);
for
(
iter
=
conflict_set
->
next
;
iter
;
iter
=
iter
->
next
)
{
/* We don't immediately set the effective policy to 'ask,
because */
rc
=
gpgsql_exec_printf
(
dbs
->
db
,
NULL
,
NULL
,
&
sqerr
,
"update bindings set effective_policy = %d, conflict = %Q"
" where email = %Q and fingerprint = %Q and effective_policy != %d;"
,
TOFU_POLICY_NONE
,
fingerprint
,
email
,
iter
->
d
,
TOFU_POLICY_ASK
);
if
(
rc
)
{
log_error
(
_
(
"error changing TOFU policy: %s
\n
"
),
sqerr
);
print_further_info
(
"binding: <key: %s, user id: %s>"
,
fingerprint
,
user_id
);
sqlite3_free
(
sqerr
);
sqerr
=
NULL
;
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
}
else
if
(
DBG_TRUST
)
log_debug
(
"Set %s to conflict with %s
\n
"
,
iter
->
d
,
fingerprint
);
}
out
:
if
(
in_transaction
)
end_transaction
(
ctrl
,
0
);
if
(
policyp
)
*
policyp
=
policy
;
if
(
conflict_setp
)
*
conflict_setp
=
conflict_set
;
else
free_strlist
(
conflict_set
);
return
trust_level
;
}
/* Return a malloced string of the form
* "7~months"
* The caller should replace all '~' in the returned string by a space
* and also free the returned string.
*
* This is actually a bad hack which may not work correctly with all
* languages.
*/
static
char
*
time_ago_str
(
long
long
int
t
)
{
/* It would be nice to use a macro to do this, but gettext
works on the unpreprocessed code. */
#define MIN_SECS (60)
#define HOUR_SECS (60 * MIN_SECS)
#define DAY_SECS (24 * HOUR_SECS)
#define WEEK_SECS (7 * DAY_SECS)
#define MONTH_SECS (30 * DAY_SECS)
#define YEAR_SECS (365 * DAY_SECS)
if
(
t
>
2
*
YEAR_SECS
)
{
long
long
int
c
=
t
/
YEAR_SECS
;
return
xtryasprintf
(
ngettext
(
"%lld~year"
,
"%lld~years"
,
c
),
c
);
}
if
(
t
>
2
*
MONTH_SECS
)
{
long
long
int
c
=
t
/
MONTH_SECS
;
return
xtryasprintf
(
ngettext
(
"%lld~month"
,
"%lld~months"
,
c
),
c
);
}
if
(
t
>
2
*
WEEK_SECS
)
{
long
long
int
c
=
t
/
WEEK_SECS
;
return
xtryasprintf
(
ngettext
(
"%lld~week"
,
"%lld~weeks"
,
c
),
c
);
}
if
(
t
>
2
*
DAY_SECS
)
{
long
long
int
c
=
t
/
DAY_SECS
;
return
xtryasprintf
(
ngettext
(
"%lld~day"
,
"%lld~days"
,
c
),
c
);
}
if
(
t
>
2
*
HOUR_SECS
)
{
long
long
int
c
=
t
/
HOUR_SECS
;
return
xtryasprintf
(
ngettext
(
"%lld~hour"
,
"%lld~hours"
,
c
),
c
);
}
if
(
t
>
2
*
MIN_SECS
)
{
long
long
int
c
=
t
/
MIN_SECS
;
return
xtryasprintf
(
ngettext
(
"%lld~minute"
,
"%lld~minutes"
,
c
),
c
);
}
return
xtryasprintf
(
ngettext
(
"%lld~second"
,
"%lld~seconds"
,
t
),
t
);
}
/* If FP is NULL, write TOFU_STATS status line. If FP is not NULL
* write a "tfs" record to that stream. */
static
void
write_stats_status
(
estream_t
fp
,
enum
tofu_policy
policy
,
unsigned
long
signature_count
,
unsigned
long
signature_first_seen
,
unsigned
long
signature_most_recent
,
unsigned
long
signature_days
,
unsigned
long
encryption_count
,
unsigned
long
encryption_first_done
,
unsigned
long
encryption_most_recent
,
unsigned
long
encryption_days
)
{
int
summary
;
int
validity
;
unsigned
long
days_sq
;
/* Use the euclidean distance (m = sqrt(a^2 + b^2)) rather then the
sum of the magnitudes (m = a + b) to ensure a balance between
verified signatures and encrypted messages. */
days_sq
=
signature_days
*
signature_days
+
encryption_days
*
encryption_days
;
if
(
days_sq
<
1
)
validity
=
1
;
/* Key without history. */
else
if
(
days_sq
<
(
2
*
BASIC_TRUST_THRESHOLD
)
*
(
2
*
BASIC_TRUST_THRESHOLD
))
validity
=
2
;
/* Key with too little history. */
else
if
(
days_sq
<
(
2
*
FULL_TRUST_THRESHOLD
)
*
(
2
*
FULL_TRUST_THRESHOLD
))
validity
=
3
;
/* Key with enough history for basic trust. */
else
validity
=
4
;
/* Key with a lot of history. */
if
(
policy
==
TOFU_POLICY_ASK
)
summary
=
0
;
/* Key requires attention. */
else
summary
=
validity
;
if
(
fp
)
{
es_fprintf
(
fp
,
"tfs:1:%d:%lu:%lu:%s:%lu:%lu:%lu:%lu:%d:%lu:%lu:
\n
"
,
summary
,
signature_count
,
encryption_count
,
tofu_policy_str
(
policy
),
signature_first_seen
,
signature_most_recent
,
encryption_first_done
,
encryption_most_recent
,
validity
,
signature_days
,
encryption_days
);
}
else
{
write_status_printf
(
STATUS_TOFU_STATS
,
"%d %lu %lu %s %lu %lu %lu %lu %d %lu %lu"
,
summary
,
signature_count
,
encryption_count
,
tofu_policy_str
(
policy
),
signature_first_seen
,
signature_most_recent
,
encryption_first_done
,
encryption_most_recent
,
validity
,
signature_days
,
encryption_days
);
}
}
/* Note: If OUTFP is not NULL, this function merely prints a "tfs" record
* to OUTFP.
*
* POLICY is the key's policy (as returned by get_policy).
*
* Returns 0 if ONLY_STATUS_FD is set. Otherwise, returns whether
* the caller should call show_warning after iterating over all user
* ids.
*/
static
int
show_statistics
(
tofu_dbs_t
dbs
,
const
char
*
fingerprint
,
const
char
*
email
,
enum
tofu_policy
policy
,
estream_t
outfp
,
int
only_status_fd
,
time_t
now
)
{
char
*
fingerprint_pp
;
int
rc
;
strlist_t
strlist
=
NULL
;
char
*
err
=
NULL
;
unsigned
long
signature_first_seen
=
0
;
unsigned
long
signature_most_recent
=
0
;
unsigned
long
signature_count
=
0
;
unsigned
long
signature_days
=
0
;
unsigned
long
encryption_first_done
=
0
;
unsigned
long
encryption_most_recent
=
0
;
unsigned
long
encryption_count
=
0
;
unsigned
long
encryption_days
=
0
;
int
show_warning
=
0
;
if
(
only_status_fd
&&
!
is_status_enabled
())
return
0
;
fingerprint_pp
=
format_hexfingerprint
(
fingerprint
,
NULL
,
0
);
/* Get the signature stats. */
rc
=
gpgsql_exec_printf
(
dbs
->
db
,
strings_collect_cb
,
&
strlist
,
&
err
,
"select count (*), coalesce (min (signatures.time), 0),
\n
"
" coalesce (max (signatures.time), 0)
\n
"
" from signatures
\n
"
" left join bindings on signatures.binding = bindings.oid
\n
"
" where fingerprint = %Q and email = %Q;"
,
fingerprint
,
email
);
if
(
rc
)
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
err
);
print_further_info
(
"getting signature statistics"
);
sqlite3_free
(
err
);
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
goto
out
;
}
rc
=
gpgsql_exec_printf
(
dbs
->
db
,
strings_collect_cb
,
&
strlist
,
&
err
,
"select count (*) from
\n
"
" (select round(signatures.time / (24 * 60 * 60)) day
\n
"
" from signatures
\n
"
" left join bindings on signatures.binding = bindings.oid
\n
"
" where fingerprint = %Q and email = %Q
\n
"
" group by day);"
,
fingerprint
,
email
);
if
(
rc
)
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
err
);
print_further_info
(
"getting signature statistics (by day)"
);
sqlite3_free
(
err
);
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
goto
out
;
}
if
(
strlist
)
{
/* We expect exactly 4 elements. */
log_assert
(
strlist
->
next
);
log_assert
(
strlist
->
next
->
next
);
log_assert
(
strlist
->
next
->
next
->
next
);
log_assert
(
!
strlist
->
next
->
next
->
next
->
next
);
string_to_ulong
(
&
signature_days
,
strlist
->
d
,
-1
,
__LINE__
);
string_to_ulong
(
&
signature_count
,
strlist
->
next
->
d
,
-1
,
__LINE__
);
string_to_ulong
(
&
signature_first_seen
,
strlist
->
next
->
next
->
d
,
-1
,
__LINE__
);
string_to_ulong
(
&
signature_most_recent
,
strlist
->
next
->
next
->
next
->
d
,
-1
,
__LINE__
);
free_strlist
(
strlist
);
strlist
=
NULL
;
}
/* Get the encryption stats. */
rc
=
gpgsql_exec_printf
(
dbs
->
db
,
strings_collect_cb
,
&
strlist
,
&
err
,
"select count (*), coalesce (min (encryptions.time), 0),
\n
"
" coalesce (max (encryptions.time), 0)
\n
"
" from encryptions
\n
"
" left join bindings on encryptions.binding = bindings.oid
\n
"
" where fingerprint = %Q and email = %Q;"
,
fingerprint
,
email
);
if
(
rc
)
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
err
);
print_further_info
(
"getting encryption statistics"
);
sqlite3_free
(
err
);
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
goto
out
;
}
rc
=
gpgsql_exec_printf
(
dbs
->
db
,
strings_collect_cb
,
&
strlist
,
&
err
,
"select count (*) from
\n
"
" (select round(encryptions.time / (24 * 60 * 60)) day
\n
"
" from encryptions
\n
"
" left join bindings on encryptions.binding = bindings.oid
\n
"
" where fingerprint = %Q and email = %Q
\n
"
" group by day);"
,
fingerprint
,
email
);
if
(
rc
)
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
err
);
print_further_info
(
"getting encryption statistics (by day)"
);
sqlite3_free
(
err
);
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
goto
out
;
}
if
(
strlist
)
{
/* We expect exactly 4 elements. */
log_assert
(
strlist
->
next
);
log_assert
(
strlist
->
next
->
next
);
log_assert
(
strlist
->
next
->
next
->
next
);
log_assert
(
!
strlist
->
next
->
next
->
next
->
next
);
string_to_ulong
(
&
encryption_days
,
strlist
->
d
,
-1
,
__LINE__
);
string_to_ulong
(
&
encryption_count
,
strlist
->
next
->
d
,
-1
,
__LINE__
);
string_to_ulong
(
&
encryption_first_done
,
strlist
->
next
->
next
->
d
,
-1
,
__LINE__
);
string_to_ulong
(
&
encryption_most_recent
,
strlist
->
next
->
next
->
next
->
d
,
-1
,
__LINE__
);
free_strlist
(
strlist
);
strlist
=
NULL
;
}
if
(
!
outfp
)
write_status_text_and_buffer
(
STATUS_TOFU_USER
,
fingerprint
,
email
,
strlen
(
email
),
0
);
write_stats_status
(
outfp
,
policy
,
signature_count
,
signature_first_seen
,
signature_most_recent
,
signature_days
,
encryption_count
,
encryption_first_done
,
encryption_most_recent
,
encryption_days
);
if
(
!
outfp
&&
!
only_status_fd
)
{
estream_t
fp
;
char
*
msg
;
fp
=
es_fopenmem
(
0
,
"rw,samethread"
);
if
(
!
fp
)
log_fatal
(
"error creating memory stream: %s
\n
"
,
gpg_strerror
(
gpg_error_from_syserror
()));
if
(
signature_count
==
0
&&
encryption_count
==
0
)
{
es_fprintf
(
fp
,
_
(
"%s: Verified 0~signatures and encrypted 0~messages."
),
email
);
}
else
{
if
(
signature_count
==
0
)
es_fprintf
(
fp
,
_
(
"%s: Verified 0 signatures."
),
email
);
else
{
/* Note: Translation not possible with that wording. */
char
*
ago_str
=
time_ago_str
(
now
-
signature_first_seen
);
es_fprintf
(
fp
,
"%s: Verified %ld~signatures in the past %s."
,
email
,
signature_count
,
ago_str
);
xfree
(
ago_str
);
}
es_fputs
(
" "
,
fp
);
if
(
encryption_count
==
0
)
es_fprintf
(
fp
,
_
(
"Encrypted 0 messages."
));
else
{
char
*
ago_str
=
time_ago_str
(
now
-
encryption_first_done
);
/* Note: Translation not possible with this kind of
* composition. */
es_fprintf
(
fp
,
"Encrypted %ld~messages in the past %s."
,
encryption_count
,
ago_str
);
xfree
(
ago_str
);
}
}
if
(
opt
.
verbose
)
{
es_fputs
(
" "
,
fp
);
es_fprintf
(
fp
,
_
(
"(policy: %s)"
),
tofu_policy_str
(
policy
));
}
es_fputs
(
"
\n
"
,
fp
);
{
char
*
tmpmsg
,
*
p
;
es_fputc
(
0
,
fp
);
if
(
es_fclose_snatch
(
fp
,
(
void
**
)
&
tmpmsg
,
NULL
))
log_fatal
(
"error snatching memory stream
\n
"
);
msg
=
format_text
(
tmpmsg
,
72
,
80
);
if
(
!
msg
)
/* FIXME: Return the error all the way up. */
log_fatal
(
"format failed: %s
\n
"
,
gpg_strerror
(
gpg_error_from_syserror
()));
es_free
(
tmpmsg
);
/* Print a status line but suppress the trailing LF.
* Spaces are not percent escaped. */
if
(
*
msg
)
write_status_buffer
(
STATUS_TOFU_STATS_LONG
,
msg
,
strlen
(
msg
)
-1
,
-1
);
/* Remove the non-breaking space markers. */
for
(
p
=
msg
;
*
p
;
p
++
)
if
(
*
p
==
'~'
)
*
p
=
' '
;
}
log_string
(
GPGRT_LOG_INFO
,
msg
);
xfree
(
msg
);
if
(
policy
==
TOFU_POLICY_AUTO
)
{
if
(
signature_count
==
0
)
log_info
(
_
(
"Warning: we have yet to see"
" a message signed using this key and user id!
\n
"
));
else
if
(
signature_count
==
1
)
log_info
(
_
(
"Warning: we've only seen one message"
" signed using this key and user id!
\n
"
));
if
(
encryption_count
==
0
)
log_info
(
_
(
"Warning: you have yet to encrypt"
" a message to this key!
\n
"
));
else
if
(
encryption_count
==
1
)
log_info
(
_
(
"Warning: you have only encrypted"
" one message to this key!
\n
"
));
/* Cf. write_stats_status */
if
((
encryption_count
*
encryption_count
+
signature_count
*
signature_count
)
<
((
2
*
BASIC_TRUST_THRESHOLD
)
*
(
2
*
BASIC_TRUST_THRESHOLD
)))
show_warning
=
1
;
}
}
out
:
xfree
(
fingerprint_pp
);
return
show_warning
;
}
static
void
show_warning
(
const
char
*
fingerprint
,
strlist_t
user_id_list
)
{
char
*
set_policy_command
;
char
*
text
;
char
*
tmpmsg
;
set_policy_command
=
xasprintf
(
"gpg --tofu-policy bad %s"
,
fingerprint
);
tmpmsg
=
xasprintf
(
ngettext
(
"Warning: if you think you've seen more signatures "
"by this key and user id, then this key might be a "
"forgery! Carefully examine the email address for small "
"variations. If the key is suspect, then use
\n
"
" %s
\n
"
"to mark it as being bad.
\n
"
,
"Warning: if you think you've seen more signatures "
"by this key and these user ids, then this key might be a "
"forgery! Carefully examine the email addresses for small "
"variations. If the key is suspect, then use
\n
"
" %s
\n
"
"to mark it as being bad.
\n
"
,
strlist_length
(
user_id_list
)),
set_policy_command
);
text
=
format_text
(
tmpmsg
,
72
,
80
);
if
(
!
text
)
/* FIXME: Return the error all the way up. */
log_fatal
(
"format failed: %s
\n
"
,
gpg_strerror
(
gpg_error_from_syserror
()));
xfree
(
tmpmsg
);
log_string
(
GPGRT_LOG_INFO
,
text
);
xfree
(
text
);
es_free
(
set_policy_command
);
}
/* Extract the email address from a user id and normalize it. If the
user id doesn't contain an email address, then we use the whole
user_id and normalize that. The returned string must be freed. */
static
char
*
email_from_user_id
(
const
char
*
user_id
)
{
char
*
email
=
mailbox_from_userid
(
user_id
);
if
(
!
email
)
{
/* Hmm, no email address was provided or we are out of core. Just
take the lower-case version of the whole user id. It could be
a hostname, for instance. */
email
=
ascii_strlwr
(
xstrdup
(
user_id
));
}
return
email
;
}
/* Register the signature with the bindings <fingerprint, USER_ID>,
for each USER_ID in USER_ID_LIST. The fingerprint is taken from
the primary key packet PK.
SIG_DIGEST_BIN is the binary representation of the message's
digest. SIG_DIGEST_BIN_LEN is its length.
SIG_TIME is the time that the signature was generated.
ORIGIN is a free-formed string describing the origin of the
signature. If this was from an email and the Claws MUA was used,
then this should be something like: "email:claws". If this is
NULL, the default is simply "unknown".
If MAY_ASK is 1, then this function may interact with the user.
This is necessary if there is a conflict or the binding's policy is
TOFU_POLICY_ASK.
This function returns 0 on success and an error code if an error
occurred. */
gpg_error_t
tofu_register_signature
(
ctrl_t
ctrl
,
PKT_public_key
*
pk
,
strlist_t
user_id_list
,
const
byte
*
sig_digest_bin
,
int
sig_digest_bin_len
,
time_t
sig_time
,
const
char
*
origin
)
{
time_t
now
=
gnupg_get_time
();
gpg_error_t
rc
;
tofu_dbs_t
dbs
;
char
*
fingerprint
=
NULL
;
strlist_t
user_id
;
char
*
email
=
NULL
;
char
*
sqlerr
=
NULL
;
char
*
sig_digest
=
NULL
;
unsigned
long
c
;
dbs
=
opendbs
(
ctrl
);
if
(
!
dbs
)
{
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
log_error
(
_
(
"error opening TOFU database: %s
\n
"
),
gpg_strerror
(
rc
));
return
rc
;
}
/* We do a query and then an insert. Make sure they are atomic
by wrapping them in a transaction. */
rc
=
begin_transaction
(
ctrl
,
0
);
if
(
rc
)
return
rc
;
log_assert
(
pk_is_primary
(
pk
));
sig_digest
=
make_radix64_string
(
sig_digest_bin
,
sig_digest_bin_len
);
if
(
!
sig_digest
)
{
rc
=
gpg_error_from_syserror
();
goto
leave
;
}
fingerprint
=
hexfingerprint
(
pk
,
NULL
,
0
);
if
(
!
fingerprint
)
{
rc
=
gpg_error_from_syserror
();
goto
leave
;
}
if
(
!
origin
)
origin
=
"unknown"
;
/* The default origin is simply "unknown". */
for
(
user_id
=
user_id_list
;
user_id
;
user_id
=
user_id
->
next
)
{
email
=
email_from_user_id
(
user_id
->
d
);
if
(
DBG_TRUST
)
log_debug
(
"TOFU: Registering signature %s with binding"
" <key: %s, user id: %s>
\n
"
,
sig_digest
,
fingerprint
,
email
);
/* Make sure the binding exists and record any TOFU
conflicts. */
if
(
get_trust
(
ctrl
,
pk
,
fingerprint
,
email
,
user_id
->
d
,
0
,
NULL
,
NULL
,
now
)
==
_tofu_GET_TRUST_ERROR
)
{
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
xfree
(
email
);
break
;
}
/* If we've already seen this signature before, then don't add
it again. */
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
register_already_seen
,
get_single_unsigned_long_cb2
,
&
c
,
&
sqlerr
,
"select count (*)
\n
"
" from signatures left join bindings
\n
"
" on signatures.binding = bindings.oid
\n
"
" where fingerprint = ? and email = ? and sig_time = ?
\n
"
" and sig_digest = ?"
,
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_STRING
,
email
,
GPGSQL_ARG_LONG_LONG
,
(
long
long
)
sig_time
,
GPGSQL_ARG_STRING
,
sig_digest
,
GPGSQL_ARG_END
);
if
(
rc
)
{
log_error
(
_
(
"error reading TOFU database: %s
\n
"
),
sqlerr
);
print_further_info
(
"checking existence"
);
sqlite3_free
(
sqlerr
);
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
}
else
if
(
c
>
1
)
/* Duplicates! This should not happen. In particular,
because <fingerprint, email, sig_time, sig_digest> is the
primary key! */
log_debug
(
"SIGNATURES DB contains duplicate records"
" <key: %s, email: %s, time: 0x%lx, sig: %s,"
" origin: %s>."
" Please report.
\n
"
,
fingerprint
,
email
,
(
unsigned
long
)
sig_time
,
sig_digest
,
origin
);
else
if
(
c
==
1
)
{
if
(
DBG_TRUST
)
log_debug
(
"Already observed the signature and binding"
" <key: %s, email: %s, time: 0x%lx, sig: %s,"
" origin: %s>
\n
"
,
fingerprint
,
email
,
(
unsigned
long
)
sig_time
,
sig_digest
,
origin
);
}
else
if
(
opt
.
dry_run
)
{
log_info
(
"TOFU database update skipped due to --dry-run
\n
"
);
}
else
/* This is the first time that we've seen this signature and
binding. Record it. */
{
if
(
DBG_TRUST
)
log_debug
(
"TOFU: Saving signature"
" <key: %s, user id: %s, sig: %s>
\n
"
,
fingerprint
,
email
,
sig_digest
);
log_assert
(
c
==
0
);
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
register_signature
,
NULL
,
NULL
,
&
sqlerr
,
"insert into signatures
\n
"
" (binding, sig_digest, origin, sig_time, time)
\n
"
" values
\n
"
" ((select oid from bindings
\n
"
" where fingerprint = ? and email = ?),
\n
"
" ?, ?, ?, ?);"
,
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_STRING
,
email
,
GPGSQL_ARG_STRING
,
sig_digest
,
GPGSQL_ARG_STRING
,
origin
,
GPGSQL_ARG_LONG_LONG
,
(
long
long
)
sig_time
,
GPGSQL_ARG_LONG_LONG
,
(
long
long
)
now
,
GPGSQL_ARG_END
);
if
(
rc
)
{
log_error
(
_
(
"error updating TOFU database: %s
\n
"
),
sqlerr
);
print_further_info
(
"insert signatures"
);
sqlite3_free
(
sqlerr
);
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
}
}
xfree
(
email
);
if
(
rc
)
break
;
}
leave
:
if
(
rc
)
rollback_transaction
(
ctrl
);
else
rc
=
end_transaction
(
ctrl
,
0
);
xfree
(
fingerprint
);
xfree
(
sig_digest
);
return
rc
;
}
gpg_error_t
tofu_register_encryption
(
ctrl_t
ctrl
,
PKT_public_key
*
pk
,
strlist_t
user_id_list
,
int
may_ask
)
{
time_t
now
=
gnupg_get_time
();
gpg_error_t
rc
=
0
;
tofu_dbs_t
dbs
;
kbnode_t
kb
=
NULL
;
int
free_user_id_list
=
0
;
char
*
fingerprint
=
NULL
;
strlist_t
user_id
;
char
*
sqlerr
=
NULL
;
int
in_batch
=
0
;
dbs
=
opendbs
(
ctrl
);
if
(
!
dbs
)
{
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
log_error
(
_
(
"error opening TOFU database: %s
\n
"
),
gpg_strerror
(
rc
));
return
rc
;
}
if
(
/* We need the key block to find the primary key. */
!
pk_is_primary
(
pk
)
/* We need the key block to find all user ids. */
||
!
user_id_list
)
kb
=
get_pubkeyblock
(
ctrl
,
pk
->
keyid
);
/* Make sure PK is a primary key. */
if
(
!
pk_is_primary
(
pk
))
pk
=
kb
->
pkt
->
pkt
.
public_key
;
if
(
!
user_id_list
)
{
/* Use all non-revoked user ids. Do use expired user ids. */
kbnode_t
n
=
kb
;
while
((
n
=
find_next_kbnode
(
n
,
PKT_USER_ID
)))
{
PKT_user_id
*
uid
=
n
->
pkt
->
pkt
.
user_id
;
if
(
uid
->
flags
.
revoked
)
continue
;
add_to_strlist
(
&
user_id_list
,
uid
->
name
);
}
free_user_id_list
=
1
;
if
(
!
user_id_list
)
log_info
(
_
(
"WARNING: Encrypting to %s, which has no "
"non-revoked user ids
\n
"
),
keystr
(
pk
->
keyid
));
}
fingerprint
=
hexfingerprint
(
pk
,
NULL
,
0
);
if
(
!
fingerprint
)
{
rc
=
gpg_error_from_syserror
();
goto
leave
;
}
tofu_begin_batch_update
(
ctrl
);
in_batch
=
1
;
tofu_resume_batch_transaction
(
ctrl
);
for
(
user_id
=
user_id_list
;
user_id
;
user_id
=
user_id
->
next
)
{
char
*
email
=
email_from_user_id
(
user_id
->
d
);
strlist_t
conflict_set
=
NULL
;
enum
tofu_policy
policy
;
/* Make sure the binding exists and that we recognize any
conflicts. */
int
tl
=
get_trust
(
ctrl
,
pk
,
fingerprint
,
email
,
user_id
->
d
,
may_ask
,
&
policy
,
&
conflict_set
,
now
);
if
(
tl
==
_tofu_GET_TRUST_ERROR
)
{
/* An error. */
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
xfree
(
email
);
goto
leave
;
}
/* If there is a conflict and MAY_ASK is true, we need to show
* the TOFU statistics for the current binding and the
* conflicting bindings. But, if we are not in batch mode, then
* they have already been printed (this is required to make sure
* the information is available to the caller before cpr_get is
* called). */
if
(
policy
==
TOFU_POLICY_ASK
&&
may_ask
&&
opt
.
batch
)
{
strlist_t
iter
;
/* The conflict set should contain at least the current
* key. */
log_assert
(
conflict_set
);
for
(
iter
=
conflict_set
;
iter
;
iter
=
iter
->
next
)
show_statistics
(
dbs
,
iter
->
d
,
email
,
TOFU_POLICY_ASK
,
NULL
,
1
,
now
);
}
free_strlist
(
conflict_set
);
rc
=
gpgsql_stepx
(
dbs
->
db
,
&
dbs
->
s
.
register_encryption
,
NULL
,
NULL
,
&
sqlerr
,
"insert into encryptions
\n
"
" (binding, time)
\n
"
" values
\n
"
" ((select oid from bindings
\n
"
" where fingerprint = ? and email = ?),
\n
"
" ?);"
,
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_STRING
,
email
,
GPGSQL_ARG_LONG_LONG
,
(
long
long
)
now
,
GPGSQL_ARG_END
);
if
(
rc
)
{
log_error
(
_
(
"error updating TOFU database: %s
\n
"
),
sqlerr
);
print_further_info
(
"insert encryption"
);
sqlite3_free
(
sqlerr
);
rc
=
gpg_error
(
GPG_ERR_GENERAL
);
}
xfree
(
email
);
}
leave
:
if
(
in_batch
)
tofu_end_batch_update
(
ctrl
);
release_kbnode
(
kb
);
if
(
free_user_id_list
)
free_strlist
(
user_id_list
);
xfree
(
fingerprint
);
return
rc
;
}
/* Combine a trust level returned from the TOFU trust model with a
trust level returned by the PGP trust model. This is primarily of
interest when the trust model is tofu+pgp (TM_TOFU_PGP).
This function ors together the upper bits (the values not covered
by TRUST_MASK, i.e., TRUST_FLAG_REVOKED, etc.). */
int
tofu_wot_trust_combine
(
int
tofu_base
,
int
wot_base
)
{
int
tofu
=
tofu_base
&
TRUST_MASK
;
int
wot
=
wot_base
&
TRUST_MASK
;
int
upper
=
(
tofu_base
&
~
TRUST_MASK
)
|
(
wot_base
&
~
TRUST_MASK
);
log_assert
(
tofu
==
TRUST_UNKNOWN
||
tofu
==
TRUST_EXPIRED
||
tofu
==
TRUST_UNDEFINED
||
tofu
==
TRUST_NEVER
||
tofu
==
TRUST_MARGINAL
||
tofu
==
TRUST_FULLY
||
tofu
==
TRUST_ULTIMATE
);
log_assert
(
wot
==
TRUST_UNKNOWN
||
wot
==
TRUST_EXPIRED
||
wot
==
TRUST_UNDEFINED
||
wot
==
TRUST_NEVER
||
wot
==
TRUST_MARGINAL
||
wot
==
TRUST_FULLY
||
wot
==
TRUST_ULTIMATE
);
/* We first consider negative trust policys. These trump positive
trust policies. */
if
(
tofu
==
TRUST_NEVER
||
wot
==
TRUST_NEVER
)
/* TRUST_NEVER trumps everything else. */
return
upper
|
TRUST_NEVER
;
if
(
tofu
==
TRUST_EXPIRED
||
wot
==
TRUST_EXPIRED
)
/* TRUST_EXPIRED trumps everything but TRUST_NEVER. */
return
upper
|
TRUST_EXPIRED
;
/* Now we only have positive or neutral trust policies. We take
the max. */
if
(
tofu
==
TRUST_ULTIMATE
)
return
upper
|
TRUST_ULTIMATE
|
TRUST_FLAG_TOFU_BASED
;
if
(
wot
==
TRUST_ULTIMATE
)
return
upper
|
TRUST_ULTIMATE
;
if
(
tofu
==
TRUST_FULLY
)
return
upper
|
TRUST_FULLY
|
TRUST_FLAG_TOFU_BASED
;
if
(
wot
==
TRUST_FULLY
)
return
upper
|
TRUST_FULLY
;
if
(
tofu
==
TRUST_MARGINAL
)
return
upper
|
TRUST_MARGINAL
|
TRUST_FLAG_TOFU_BASED
;
if
(
wot
==
TRUST_MARGINAL
)
return
upper
|
TRUST_MARGINAL
;
if
(
tofu
==
TRUST_UNDEFINED
)
return
upper
|
TRUST_UNDEFINED
|
TRUST_FLAG_TOFU_BASED
;
if
(
wot
==
TRUST_UNDEFINED
)
return
upper
|
TRUST_UNDEFINED
;
return
upper
|
TRUST_UNKNOWN
;
}
/* Write a "tfs" record for a --with-colons listing. */
gpg_error_t
tofu_write_tfs_record
(
ctrl_t
ctrl
,
estream_t
fp
,
PKT_public_key
*
pk
,
const
char
*
user_id
)
{
time_t
now
=
gnupg_get_time
();
gpg_error_t
err
=
0
;
tofu_dbs_t
dbs
;
char
*
fingerprint
;
char
*
email
=
NULL
;
enum
tofu_policy
policy
;
if
(
!*
user_id
)
return
0
;
/* No TOFU stats possible for an empty ID. */
dbs
=
opendbs
(
ctrl
);
if
(
!
dbs
)
{
err
=
gpg_error
(
GPG_ERR_GENERAL
);
log_error
(
_
(
"error opening TOFU database: %s
\n
"
),
gpg_strerror
(
err
));
return
err
;
}
fingerprint
=
hexfingerprint
(
pk
,
NULL
,
0
);
if
(
!
fingerprint
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
email
=
email_from_user_id
(
user_id
);
policy
=
get_policy
(
ctrl
,
dbs
,
pk
,
fingerprint
,
user_id
,
email
,
NULL
,
now
);
show_statistics
(
dbs
,
fingerprint
,
email
,
policy
,
fp
,
0
,
now
);
leave
:
xfree
(
email
);
xfree
(
fingerprint
);
return
err
;
}
/* Return the validity (TRUST_NEVER, etc.) of the bindings
<FINGERPRINT, USER_ID>, for each USER_ID in USER_ID_LIST. If
USER_ID_LIST->FLAG is set, then the id is considered to be expired.
PK is the primary key packet.
If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user
will be prompted to choose a policy. If MAY_ASK is 0 and the
policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned.
Returns TRUST_UNDEFINED if an error occurs.
Fixme: eturn an error code
*/
int
tofu_get_validity
(
ctrl_t
ctrl
,
PKT_public_key
*
pk
,
strlist_t
user_id_list
,
int
may_ask
)
{
time_t
now
=
gnupg_get_time
();
tofu_dbs_t
dbs
;
char
*
fingerprint
=
NULL
;
strlist_t
user_id
;
int
trust_level
=
TRUST_UNKNOWN
;
int
bindings
=
0
;
int
bindings_valid
=
0
;
int
need_warning
=
0
;
int
had_conflict
=
0
;
dbs
=
opendbs
(
ctrl
);
if
(
!
dbs
)
{
log_error
(
_
(
"error opening TOFU database: %s
\n
"
),
gpg_strerror
(
GPG_ERR_GENERAL
));
return
TRUST_UNDEFINED
;
}
fingerprint
=
hexfingerprint
(
pk
,
NULL
,
0
);
if
(
!
fingerprint
)
log_fatal
(
"%s: malloc failed
\n
"
,
__func__
);
tofu_begin_batch_update
(
ctrl
);
/* Start the batch transaction now. */
tofu_resume_batch_transaction
(
ctrl
);
for
(
user_id
=
user_id_list
;
user_id
;
user_id
=
user_id
->
next
,
bindings
++
)
{
char
*
email
=
email_from_user_id
(
user_id
->
d
);
strlist_t
conflict_set
=
NULL
;
enum
tofu_policy
policy
;
/* Always call get_trust to make sure the binding is
registered. */
int
tl
=
get_trust
(
ctrl
,
pk
,
fingerprint
,
email
,
user_id
->
d
,
may_ask
,
&
policy
,
&
conflict_set
,
now
);
if
(
tl
==
_tofu_GET_TRUST_ERROR
)
{
/* An error. */
trust_level
=
TRUST_UNDEFINED
;
xfree
(
email
);
goto
die
;
}
if
(
DBG_TRUST
)
log_debug
(
"TOFU: validity for <key: %s, user id: %s>: %s%s.
\n
"
,
fingerprint
,
email
,
trust_value_to_string
(
tl
),
user_id
->
flags
?
" (but expired)"
:
""
);
if
(
user_id
->
flags
)
tl
=
TRUST_EXPIRED
;
if
(
tl
!=
TRUST_EXPIRED
)
bindings_valid
++
;
if
(
may_ask
&&
tl
!=
TRUST_ULTIMATE
&&
tl
!=
TRUST_EXPIRED
)
{
/* If policy is ask, then we already printed out the
* conflict information in ask_about_binding or will do so
* in a moment. */
if
(
policy
!=
TOFU_POLICY_ASK
)
need_warning
|=
show_statistics
(
dbs
,
fingerprint
,
email
,
policy
,
NULL
,
0
,
now
);
/* If there is a conflict and MAY_ASK is true, we need to
* show the TOFU statistics for the current binding and the
* conflicting bindings. But, if we are not in batch mode,
* then they have already been printed (this is required to
* make sure the information is available to the caller
* before cpr_get is called). */
if
(
policy
==
TOFU_POLICY_ASK
&&
opt
.
batch
)
{
strlist_t
iter
;
/* The conflict set should contain at least the current
* key. */
log_assert
(
conflict_set
);
had_conflict
=
1
;
for
(
iter
=
conflict_set
;
iter
;
iter
=
iter
->
next
)
show_statistics
(
dbs
,
iter
->
d
,
email
,
TOFU_POLICY_ASK
,
NULL
,
1
,
now
);
}
}
free_strlist
(
conflict_set
);
if
(
tl
==
TRUST_NEVER
)
trust_level
=
TRUST_NEVER
;
else
if
(
tl
==
TRUST_EXPIRED
)
/* Ignore expired bindings in the trust calculation. */
;
else
if
(
tl
>
trust_level
)
{
/* The expected values: */
log_assert
(
tl
==
TRUST_UNKNOWN
||
tl
==
TRUST_UNDEFINED
||
tl
==
TRUST_MARGINAL
||
tl
==
TRUST_FULLY
||
tl
==
TRUST_ULTIMATE
);
/* We assume the following ordering: */
log_assert
(
TRUST_UNKNOWN
<
TRUST_UNDEFINED
);
log_assert
(
TRUST_UNDEFINED
<
TRUST_MARGINAL
);
log_assert
(
TRUST_MARGINAL
<
TRUST_FULLY
);
log_assert
(
TRUST_FULLY
<
TRUST_ULTIMATE
);
trust_level
=
tl
;
}
xfree
(
email
);
}
if
(
need_warning
&&
!
had_conflict
)
show_warning
(
fingerprint
,
user_id_list
);
die
:
tofu_end_batch_update
(
ctrl
);
xfree
(
fingerprint
);
if
(
bindings_valid
==
0
)
{
if
(
DBG_TRUST
)
log_debug
(
"no (of %d) valid bindings."
" Can't get TOFU validity for this set of user ids.
\n
"
,
bindings
);
return
TRUST_NEVER
;
}
return
trust_level
;
}
/* Set the policy for all non-revoked user ids in the keyblock KB to
POLICY.
If no key is available with the specified key id, then this
function returns GPG_ERR_NO_PUBKEY.
Returns 0 on success and an error code otherwise. */
gpg_error_t
tofu_set_policy
(
ctrl_t
ctrl
,
kbnode_t
kb
,
enum
tofu_policy
policy
)
{
gpg_error_t
err
=
0
;
time_t
now
=
gnupg_get_time
();
tofu_dbs_t
dbs
;
PKT_public_key
*
pk
;
char
*
fingerprint
=
NULL
;
log_assert
(
kb
->
pkt
->
pkttype
==
PKT_PUBLIC_KEY
);
pk
=
kb
->
pkt
->
pkt
.
public_key
;
dbs
=
opendbs
(
ctrl
);
if
(
!
dbs
)
{
log_error
(
_
(
"error opening TOFU database: %s
\n
"
),
gpg_strerror
(
GPG_ERR_GENERAL
));
return
gpg_error
(
GPG_ERR_GENERAL
);
}
if
(
DBG_TRUST
)
log_debug
(
"Setting TOFU policy for %s to %s
\n
"
,
keystr
(
pk
->
keyid
),
tofu_policy_str
(
policy
));
if
(
!
pk_is_primary
(
pk
))
log_bug
(
"%s: Passed a subkey, but expecting a primary key.
\n
"
,
__func__
);
fingerprint
=
hexfingerprint
(
pk
,
NULL
,
0
);
if
(
!
fingerprint
)
return
gpg_error_from_syserror
();
begin_transaction
(
ctrl
,
0
);
for
(;
kb
;
kb
=
kb
->
next
)
{
PKT_user_id
*
user_id
;
char
*
email
;
if
(
kb
->
pkt
->
pkttype
!=
PKT_USER_ID
)
continue
;
user_id
=
kb
->
pkt
->
pkt
.
user_id
;
if
(
user_id
->
flags
.
revoked
)
/* Skip revoked user ids. (Don't skip expired user ids, the
expiry can be changed.) */
continue
;
email
=
email_from_user_id
(
user_id
->
name
);
err
=
record_binding
(
dbs
,
fingerprint
,
email
,
user_id
->
name
,
policy
,
TOFU_POLICY_NONE
,
NULL
,
0
,
1
,
now
);
if
(
err
)
{
log_error
(
"error setting policy for key %s, user id
\"
%s
\"
: %s"
,
fingerprint
,
email
,
gpg_strerror
(
err
));
xfree
(
email
);
break
;
}
xfree
(
email
);
}
if
(
err
)
rollback_transaction
(
ctrl
);
else
end_transaction
(
ctrl
,
0
);
xfree
(
fingerprint
);
return
err
;
}
/* Return the TOFU policy for the specified binding in *POLICY. If no
policy has been set for the binding, sets *POLICY to
TOFU_POLICY_NONE.
PK is a primary public key and USER_ID is a user id.
Returns 0 on success and an error code otherwise. */
gpg_error_t
tofu_get_policy
(
ctrl_t
ctrl
,
PKT_public_key
*
pk
,
PKT_user_id
*
user_id
,
enum
tofu_policy
*
policy
)
{
time_t
now
=
gnupg_get_time
();
tofu_dbs_t
dbs
;
char
*
fingerprint
;
char
*
email
;
/* Make sure PK is a primary key. */
log_assert
(
pk_is_primary
(
pk
));
dbs
=
opendbs
(
ctrl
);
if
(
!
dbs
)
{
log_error
(
_
(
"error opening TOFU database: %s
\n
"
),
gpg_strerror
(
GPG_ERR_GENERAL
));
return
gpg_error
(
GPG_ERR_GENERAL
);
}
fingerprint
=
hexfingerprint
(
pk
,
NULL
,
0
);
if
(
!
fingerprint
)
return
gpg_error_from_syserror
();
email
=
email_from_user_id
(
user_id
->
name
);
*
policy
=
get_policy
(
ctrl
,
dbs
,
pk
,
fingerprint
,
user_id
->
name
,
email
,
NULL
,
now
);
xfree
(
email
);
xfree
(
fingerprint
);
if
(
*
policy
==
_tofu_GET_POLICY_ERROR
)
return
gpg_error
(
GPG_ERR_GENERAL
);
return
0
;
}
gpg_error_t
tofu_notice_key_changed
(
ctrl_t
ctrl
,
kbnode_t
kb
)
{
tofu_dbs_t
dbs
;
PKT_public_key
*
pk
;
char
*
fingerprint
;
char
*
sqlerr
=
NULL
;
int
rc
;
/* Make sure PK is a primary key. */
setup_main_keyids
(
kb
);
pk
=
kb
->
pkt
->
pkt
.
public_key
;
log_assert
(
pk_is_primary
(
pk
));
dbs
=
opendbs
(
ctrl
);
if
(
!
dbs
)
{
log_error
(
_
(
"error opening TOFU database: %s
\n
"
),
gpg_strerror
(
GPG_ERR_GENERAL
));
return
gpg_error
(
GPG_ERR_GENERAL
);
}
fingerprint
=
hexfingerprint
(
pk
,
NULL
,
0
);
if
(
!
fingerprint
)
return
gpg_error_from_syserror
();
rc
=
gpgsql_stepx
(
dbs
->
db
,
NULL
,
NULL
,
NULL
,
&
sqlerr
,
"update bindings set effective_policy = ?"
" where fingerprint = ?;"
,
GPGSQL_ARG_INT
,
(
int
)
TOFU_POLICY_NONE
,
GPGSQL_ARG_STRING
,
fingerprint
,
GPGSQL_ARG_END
);
xfree
(
fingerprint
);
if
(
rc
==
_tofu_GET_POLICY_ERROR
)
return
gpg_error
(
GPG_ERR_GENERAL
);
return
0
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Tue, Apr 22, 3:56 AM (10 h, 12 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
52/a9/ae04d910f3340a5a590bca93edbb
Attached To
rG GnuPG
Event Timeline
Log In to Comment