// This file is under GNU General Public License 3.0
|
|
// see LICENSE.txt
|
|
|
|
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
|
|
|
#define _GNU_SOURCE 1
|
|
|
|
#include "platform.h"
|
|
#include "pEp_internal.h"
|
|
#include "pgp_sequoia.h"
|
|
|
|
#include <limits.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "wrappers.h"
|
|
|
|
#define TRACING 0
|
|
#ifndef TRACING
|
|
# ifndef NDEBUG
|
|
# define TRACING 0
|
|
# else
|
|
# define TRACING 1
|
|
# endif
|
|
#endif
|
|
|
|
// enable tracing if in debugging mode
|
|
#if TRACING
|
|
#include "status_to_string.h"
|
|
# define _T(...) do { \
|
|
fprintf(stderr, ##__VA_ARGS__); \
|
|
} while (0)
|
|
#else
|
|
# define _T(...) do { } while (0)
|
|
#endif
|
|
|
|
// Show the start of a tracepoint (i.e., don't print a newline).
|
|
#define TC(...) do { \
|
|
_T("%s: ", __func__); \
|
|
_T(__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
// Show a trace point.
|
|
# define T(...) do { \
|
|
TC(__VA_ARGS__); \
|
|
_T("\n"); \
|
|
} while(0)
|
|
|
|
// Verbosely displays errors.
|
|
# define DUMP_STATUS(__de_sq_status, __de_pep_status, ...) do { \
|
|
TC(__VA_ARGS__); \
|
|
_T(": "); \
|
|
if (__de_sq_status) { \
|
|
_T("Sequoia: %s => ", pgp_status_to_string(__de_sq_status)); \
|
|
} \
|
|
_T("%s\n", pEp_status_to_string(__de_pep_status)); \
|
|
} while(0)
|
|
|
|
# define DUMP_ERR(__de_err, __de_status, ...) do { \
|
|
TC(__VA_ARGS__); \
|
|
_T(": "); \
|
|
if (__de_err) { \
|
|
_T("Sequoia: %s => ", pgp_error_to_string(__de_err)); \
|
|
pgp_error_free(__de_err); \
|
|
} \
|
|
_T("%s\n", pEp_status_to_string(__de_status)); \
|
|
} while(0)
|
|
|
|
// If __ec_status is an error, then disable the error, set 'status' to
|
|
// it, and jump to 'out'.
|
|
#define ERROR_OUT(__e_err, __ec_status, ...) do { \
|
|
PEP_STATUS ___ec_status = (__ec_status); \
|
|
if ((___ec_status) != PEP_STATUS_OK) { \
|
|
DUMP_ERR((__e_err), (___ec_status), ##__VA_ARGS__); \
|
|
status = (___ec_status); \
|
|
goto out; \
|
|
} \
|
|
} while(0)
|
|
|
|
int email_cmp(void *cookie, int a_len, const void *a, int b_len, const void *b)
|
|
{
|
|
pgp_packet_t a_userid = pgp_user_id_from_raw (a, a_len);
|
|
pgp_packet_t b_userid = pgp_user_id_from_raw (b, b_len);
|
|
|
|
T("(%.*s, %.*s)", a_len, (const char *) a, b_len, (const char *) b);
|
|
|
|
char *a_address = NULL;
|
|
pgp_user_id_address_normalized(NULL, a_userid, &a_address);
|
|
|
|
char *b_address = NULL;
|
|
pgp_user_id_address_normalized(NULL, b_userid, &b_address);
|
|
|
|
pgp_packet_free(a_userid);
|
|
pgp_packet_free(b_userid);
|
|
|
|
// return an integer that is negative, zero, or positive if the
|
|
// first string is less than, equal to, or greater than the
|
|
// second, respectively.
|
|
int result;
|
|
if (!a_address && !b_address)
|
|
result = 0;
|
|
else if (!a_address)
|
|
result = -1;
|
|
else if (!b_address)
|
|
result = 1;
|
|
else
|
|
result = strcmp(a_address, b_address);
|
|
|
|
if (true) {
|
|
T("'%s' %s '%s'",
|
|
a_address,
|
|
result == 0 ? "==" : result < 0 ? "<" : ">",
|
|
b_address);
|
|
}
|
|
|
|
free(a_address);
|
|
free(b_address);
|
|
|
|
return result;
|
|
}
|
|
|
|
PEP_STATUS pgp_init(PEP_SESSION session, bool in_first)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
|
|
// Create the home directory.
|
|
#ifdef _WIN32
|
|
char *home_env = getenv("LOCALAPPDATA");
|
|
#else
|
|
char *home_env = getenv("HOME");
|
|
#endif
|
|
if (!home_env)
|
|
ERROR_OUT(NULL, PEP_INIT_GPGME_INIT_FAILED, "HOME unset");
|
|
|
|
// Create the DB and initialize it.
|
|
size_t path_size = strlen(home_env)+18;
|
|
char *path = (char *) calloc(1, path_size);
|
|
assert(path);
|
|
if (!path)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
|
|
|
|
#ifdef _WIN32
|
|
int r = snprintf(path, path_size, "%s\\pEp\\.pEp_keys.db", home_env);
|
|
#else
|
|
int r = snprintf(path, path_size, "%s/.pEp_keys.db", home_env);
|
|
#endif
|
|
assert(r >= 0 && r < path_size);
|
|
if (r < 0)
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR, "snprintf");
|
|
|
|
int sqlite_result;
|
|
sqlite_result = sqlite3_open_v2(path,
|
|
&session->key_db,
|
|
SQLITE_OPEN_READWRITE
|
|
| SQLITE_OPEN_CREATE
|
|
| SQLITE_OPEN_FULLMUTEX
|
|
| SQLITE_OPEN_PRIVATECACHE,
|
|
NULL);
|
|
free(path);
|
|
if (sqlite_result != SQLITE_OK)
|
|
ERROR_OUT(NULL, PEP_INIT_CANNOT_OPEN_DB,
|
|
"opening keys DB: %s", sqlite3_errmsg(session->key_db));
|
|
|
|
sqlite_result = sqlite3_exec(session->key_db,
|
|
"PRAGMA secure_delete=true;\n"
|
|
"PRAGMA foreign_keys=true;\n"
|
|
"PRAGMA locking_mode=NORMAL;\n"
|
|
"PRAGMA journal_mode=WAL;\n",
|
|
NULL, NULL, NULL);
|
|
if (sqlite_result != SQLITE_OK)
|
|
ERROR_OUT(NULL, PEP_INIT_CANNOT_OPEN_DB,
|
|
"setting pragmas: %s", sqlite3_errmsg(session->key_db));
|
|
|
|
sqlite3_busy_timeout(session->key_db, BUSY_WAIT_TIME);
|
|
|
|
sqlite_result =
|
|
sqlite3_create_collation(session->key_db,
|
|
"EMAIL",
|
|
SQLITE_UTF8,
|
|
/* pArg (cookie) */ NULL,
|
|
email_cmp);
|
|
if (sqlite_result != SQLITE_OK)
|
|
ERROR_OUT(NULL, PEP_INIT_CANNOT_OPEN_DB,
|
|
"registering EMAIL collation function: %s",
|
|
sqlite3_errmsg(session->key_db));
|
|
|
|
sqlite_result = sqlite3_exec(session->key_db,
|
|
"CREATE TABLE IF NOT EXISTS keys (\n"
|
|
" primary_key TEXT UNIQUE PRIMARY KEY,\n"
|
|
" secret BOOLEAN,\n"
|
|
" tpk BLOB\n"
|
|
");\n"
|
|
"CREATE INDEX IF NOT EXISTS keys_index\n"
|
|
" ON keys (primary_key, secret)\n",
|
|
NULL, NULL, NULL);
|
|
if (sqlite_result != SQLITE_OK)
|
|
ERROR_OUT(NULL, PEP_INIT_CANNOT_OPEN_DB,
|
|
"creating keys table: %s",
|
|
sqlite3_errmsg(session->key_db));
|
|
|
|
sqlite_result = sqlite3_exec(session->key_db,
|
|
"CREATE TABLE IF NOT EXISTS subkeys (\n"
|
|
" subkey TEXT NOT NULL,\n"
|
|
" primary_key TEXT NOT NULL,\n"
|
|
" UNIQUE(subkey, primary_key),\n"
|
|
" FOREIGN KEY (primary_key)\n"
|
|
" REFERENCES keys(primary_key)\n"
|
|
" ON DELETE CASCADE\n"
|
|
");\n"
|
|
"CREATE INDEX IF NOT EXISTS subkeys_index\n"
|
|
" ON subkeys (subkey, primary_key)\n",
|
|
NULL, NULL, NULL);
|
|
if (sqlite_result != SQLITE_OK)
|
|
ERROR_OUT(NULL, PEP_INIT_CANNOT_OPEN_DB,
|
|
"creating subkeys table: %s",
|
|
sqlite3_errmsg(session->key_db));
|
|
|
|
sqlite_result = sqlite3_exec(session->key_db,
|
|
"CREATE TABLE IF NOT EXISTS userids (\n"
|
|
" userid TEXT NOT NULL COLLATE EMAIL,\n"
|
|
" primary_key TEXT NOT NULL,\n"
|
|
" UNIQUE(userid, primary_key),\n"
|
|
" FOREIGN KEY (primary_key)\n"
|
|
" REFERENCES keys(primary_key)\n"
|
|
" ON DELETE CASCADE\n"
|
|
");\n"
|
|
"CREATE INDEX IF NOT EXISTS userids_index\n"
|
|
" ON userids (userid COLLATE EMAIL, primary_key)\n",
|
|
NULL, NULL, NULL);
|
|
if (sqlite_result != SQLITE_OK)
|
|
ERROR_OUT(NULL, PEP_INIT_CANNOT_OPEN_DB,
|
|
"creating userids table: %s",
|
|
sqlite3_errmsg(session->key_db));
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db, "begin transaction",
|
|
-1, &session->sq_sql.begin_transaction, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db, "commit transaction",
|
|
-1, &session->sq_sql.commit_transaction, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db, "rollback transaction",
|
|
-1, &session->sq_sql.rollback_transaction, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"SELECT tpk, secret FROM keys"
|
|
" WHERE primary_key == ?",
|
|
-1, &session->sq_sql.tpk_find, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"SELECT tpk, secret FROM keys"
|
|
" WHERE primary_key == ? and secret == 1",
|
|
-1, &session->sq_sql.tsk_find, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"SELECT tpk, secret FROM subkeys"
|
|
" LEFT JOIN keys"
|
|
" ON subkeys.primary_key == keys.primary_key"
|
|
" WHERE subkey == ?",
|
|
-1, &session->sq_sql.tpk_find_by_keyid, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"SELECT tpk, secret FROM subkeys"
|
|
" LEFT JOIN keys"
|
|
" ON subkeys.primary_key == keys.primary_key"
|
|
" WHERE subkey == ?",
|
|
-1, &session->sq_sql.tpk_find_by_keyid, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"SELECT tpk, secret FROM subkeys"
|
|
" LEFT JOIN keys"
|
|
" ON subkeys.primary_key == keys.primary_key"
|
|
" WHERE subkey == ? and keys.secret == 1",
|
|
-1, &session->sq_sql.tsk_find_by_keyid, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"SELECT tpk, secret FROM userids"
|
|
" LEFT JOIN keys"
|
|
" ON userids.primary_key == keys.primary_key"
|
|
" WHERE userid == ?",
|
|
-1, &session->sq_sql.tpk_find_by_email, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"SELECT tpk, secret FROM userids"
|
|
" LEFT JOIN keys"
|
|
" ON userids.primary_key == keys.primary_key"
|
|
" WHERE userid == ? and keys.secret == 1",
|
|
-1, &session->sq_sql.tsk_find_by_email, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"select tpk, secret from keys",
|
|
-1, &session->sq_sql.tpk_all, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"select tpk, secret from keys where secret = 1",
|
|
-1, &session->sq_sql.tsk_all, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"INSERT OR REPLACE INTO keys"
|
|
" (primary_key, secret, tpk)"
|
|
" VALUES (?, ?, ?)",
|
|
-1, &session->sq_sql.tpk_save_insert_primary, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"INSERT OR REPLACE INTO subkeys"
|
|
" (subkey, primary_key)"
|
|
" VALUES (?, ?)",
|
|
-1, &session->sq_sql.tpk_save_insert_subkeys, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
sqlite_result
|
|
= sqlite3_prepare_v2(session->key_db,
|
|
"INSERT OR REPLACE INTO userids"
|
|
" (userid, primary_key)"
|
|
" VALUES (?, ?)",
|
|
-1, &session->sq_sql.tpk_save_insert_userids, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
|
|
out:
|
|
if (status != PEP_STATUS_OK)
|
|
pgp_release(session, in_first);
|
|
return status;
|
|
}
|
|
|
|
void pgp_release(PEP_SESSION session, bool out_last)
|
|
{
|
|
sqlite3_stmt **stmts = (sqlite3_stmt **) &session->sq_sql;
|
|
for (int i = 0; i < sizeof(session->sq_sql) / sizeof(*stmts); i ++)
|
|
if (stmts[i]) {
|
|
sqlite3_finalize(stmts[i]);
|
|
stmts[i] = NULL;
|
|
}
|
|
|
|
if (session->key_db) {
|
|
int result = sqlite3_close_v2(session->key_db);
|
|
if (result != 0)
|
|
DUMP_ERR(NULL, PEP_UNKNOWN_ERROR,
|
|
"Closing key DB: sqlite3_close_v2: %s",
|
|
sqlite3_errstr(result));
|
|
session->key_db = NULL;
|
|
}
|
|
}
|
|
|
|
/* commented out to omit compiler warning about unused function
|
|
|
|
// Ensures that a fingerprint is in canonical form. A canonical
|
|
// fingerprint doesn't contain any white space.
|
|
//
|
|
// This function does *not* consume fpr.
|
|
static char *pgp_fingerprint_canonicalize(const char *) __attribute__((nonnull));
|
|
static char *pgp_fingerprint_canonicalize(const char *fpr)
|
|
{
|
|
pgp_fingerprint_t pgp_fpr = pgp_fingerprint_from_hex(fpr);
|
|
char *fpr_canonicalized = pgp_fingerprint_to_hex(pgp_fpr);
|
|
pgp_fingerprint_free(pgp_fpr);
|
|
|
|
return fpr_canonicalized;
|
|
}
|
|
|
|
*/
|
|
|
|
// step statement and load the tpk and secret.
|
|
static PEP_STATUS key_load(PEP_SESSION, sqlite3_stmt *, pgp_tpk_t *, int *)
|
|
__attribute__((nonnull(1, 2)));
|
|
static PEP_STATUS key_load(PEP_SESSION session, sqlite3_stmt *stmt,
|
|
pgp_tpk_t *tpkp, int *secretp)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
int sqlite_result = sqlite3_step(stmt);
|
|
switch (sqlite_result) {
|
|
case SQLITE_ROW:
|
|
if (tpkp) {
|
|
int data_len = sqlite3_column_bytes(stmt, 0);
|
|
const void *data = sqlite3_column_blob(stmt, 0);
|
|
|
|
pgp_error_t err = NULL;
|
|
*tpkp = pgp_tpk_from_bytes(&err, data, data_len);
|
|
if (!*tpkp)
|
|
ERROR_OUT(err, PEP_GET_KEY_FAILED, "parsing TPK");
|
|
}
|
|
|
|
if (secretp)
|
|
*secretp = sqlite3_column_int(stmt, 1);
|
|
|
|
break;
|
|
case SQLITE_DONE:
|
|
// Got nothing.
|
|
status = PEP_KEY_NOT_FOUND;
|
|
break;
|
|
default:
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR,
|
|
"stepping: %s", sqlite3_errmsg(session->key_db));
|
|
}
|
|
|
|
out:
|
|
T(" -> %s", pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
// step statement until exhausted and load the tpks.
|
|
static PEP_STATUS key_loadn(PEP_SESSION, sqlite3_stmt *, pgp_tpk_t **, int *)
|
|
__attribute__((nonnull));
|
|
static PEP_STATUS key_loadn(PEP_SESSION session, sqlite3_stmt *stmt,
|
|
pgp_tpk_t **tpksp, int *tpks_countp)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
int tpks_count = 0;
|
|
int tpks_capacity = 8;
|
|
pgp_tpk_t *tpks = calloc(tpks_capacity, sizeof(pgp_tpk_t));
|
|
if (!tpks)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
|
|
|
|
for (;;) {
|
|
pgp_tpk_t tpk = NULL;
|
|
status = key_load(session, stmt, &tpk, NULL);
|
|
if (status == PEP_KEY_NOT_FOUND) {
|
|
status = PEP_STATUS_OK;
|
|
break;
|
|
}
|
|
ERROR_OUT(NULL, status, "loading TPK");
|
|
|
|
if (tpks_count == tpks_capacity) {
|
|
tpks_capacity *= 2;
|
|
tpks = realloc(tpks, sizeof(tpks[0]) * tpks_capacity);
|
|
if (!tpks)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "tpks");
|
|
}
|
|
tpks[tpks_count ++] = tpk;
|
|
}
|
|
|
|
out:
|
|
if (status != PEP_STATUS_OK) {
|
|
for (int i = 0; i < tpks_count; i ++)
|
|
pgp_tpk_free(tpks[i]);
|
|
free(tpks);
|
|
} else {
|
|
*tpksp = tpks;
|
|
*tpks_countp = tpks_count;
|
|
}
|
|
|
|
T(" -> %s (%d tpks)", pEp_status_to_string(status), *tpks_countp);
|
|
return status;
|
|
}
|
|
|
|
// Returns the TPK identified by the provided fingerprint.
|
|
//
|
|
// This function only matches on the primary key!
|
|
static PEP_STATUS tpk_find(PEP_SESSION, pgp_fingerprint_t, int, pgp_tpk_t *, int *)
|
|
__attribute__((nonnull(1, 2)));
|
|
static PEP_STATUS tpk_find(PEP_SESSION session,
|
|
pgp_fingerprint_t fpr, int private_only,
|
|
pgp_tpk_t *tpk, int *secret)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
char *fpr_str = pgp_fingerprint_to_hex(fpr);
|
|
|
|
T("(%s, %d)", fpr_str, private_only);
|
|
|
|
sqlite3_stmt *stmt
|
|
= private_only ? session->sq_sql.tsk_find : session->sq_sql.tpk_find;
|
|
sqlite3_bind_text(stmt, 1, fpr_str, -1, SQLITE_STATIC);
|
|
|
|
status = key_load(session, stmt, tpk, secret);
|
|
ERROR_OUT(NULL, status, "Looking up %s", fpr_str);
|
|
|
|
out:
|
|
sqlite3_reset(stmt);
|
|
T("(%s, %d) -> %s", fpr_str, private_only, pEp_status_to_string(status));
|
|
free(fpr_str);
|
|
return status;
|
|
}
|
|
|
|
// Returns the TPK identified by the provided keyid.
|
|
//
|
|
// This function matches on both primary keys and subkeys!
|
|
//
|
|
// Note: There can be multiple TPKs for a given keyid. This can
|
|
// occur, because an encryption subkey can be bound to multiple TPKs.
|
|
// Also, it is possible to collide key ids. If there are multiple key
|
|
// ids for a given key, this just returns one of them.
|
|
//
|
|
// If private_only is set, this will only consider TPKs with some
|
|
// secret key material.
|
|
static PEP_STATUS tpk_find_by_keyid_hex(PEP_SESSION, const char *, int, pgp_tpk_t *, int *)
|
|
__attribute__((nonnull(1, 2)));
|
|
static PEP_STATUS tpk_find_by_keyid_hex(
|
|
PEP_SESSION session, const char *keyid_hex, int private_only,
|
|
pgp_tpk_t *tpkp, int *secretp)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
T("(%s, %d)", keyid_hex, private_only);
|
|
|
|
sqlite3_stmt *stmt
|
|
= private_only ? session->sq_sql.tsk_find_by_keyid : session->sq_sql.tpk_find_by_keyid;
|
|
sqlite3_bind_text(stmt, 1, keyid_hex, -1, SQLITE_STATIC);
|
|
|
|
status = key_load(session, stmt, tpkp, secretp);
|
|
ERROR_OUT(NULL, status, "Looking up %s", keyid_hex);
|
|
|
|
out:
|
|
sqlite3_reset(stmt);
|
|
T("(%s, %d) -> %s", keyid_hex, private_only, pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
// See tpk_find_by_keyid_hex.
|
|
PEP_STATUS tpk_find_by_keyid(PEP_SESSION, pgp_keyid_t, int, pgp_tpk_t *, int *)
|
|
__attribute__((nonnull(1, 2)));
|
|
PEP_STATUS tpk_find_by_keyid(PEP_SESSION session,
|
|
pgp_keyid_t keyid, int private_only,
|
|
pgp_tpk_t *tpkp, int *secretp)
|
|
{
|
|
char *keyid_hex = pgp_keyid_to_hex(keyid);
|
|
if (! keyid_hex)
|
|
return PEP_OUT_OF_MEMORY;
|
|
PEP_STATUS status
|
|
= tpk_find_by_keyid_hex(session, keyid_hex, private_only, tpkp, secretp);
|
|
free(keyid_hex);
|
|
return status;
|
|
}
|
|
|
|
// See tpk_find_by_keyid_hex.
|
|
static PEP_STATUS tpk_find_by_fpr(PEP_SESSION, pgp_fingerprint_t, int,
|
|
pgp_tpk_t *, int *)
|
|
__attribute__((nonnull(1, 2)));
|
|
static PEP_STATUS tpk_find_by_fpr(
|
|
PEP_SESSION session, pgp_fingerprint_t fpr, int private_only,
|
|
pgp_tpk_t *tpkp, int *secretp)
|
|
{
|
|
pgp_keyid_t keyid = pgp_fingerprint_to_keyid(fpr);
|
|
if (! keyid)
|
|
return PEP_OUT_OF_MEMORY;
|
|
PEP_STATUS status
|
|
= tpk_find_by_keyid(session, keyid, private_only, tpkp, secretp);
|
|
pgp_keyid_free(keyid);
|
|
return status;
|
|
}
|
|
|
|
// See tpk_find_by_keyid_hex.
|
|
static PEP_STATUS tpk_find_by_fpr_hex(PEP_SESSION, const char *, int, pgp_tpk_t *, int *secret)
|
|
__attribute__((nonnull(1, 2)));
|
|
static PEP_STATUS tpk_find_by_fpr_hex(
|
|
PEP_SESSION session, const char *fpr, int private_only,
|
|
pgp_tpk_t *tpkp, int *secretp)
|
|
{
|
|
pgp_fingerprint_t pgp_fpr = pgp_fingerprint_from_hex(fpr);
|
|
if (! pgp_fpr)
|
|
return PEP_OUT_OF_MEMORY;
|
|
PEP_STATUS status
|
|
= tpk_find_by_fpr(session, pgp_fpr, private_only, tpkp, secretp);
|
|
pgp_fingerprint_free(pgp_fpr);
|
|
return status;
|
|
}
|
|
|
|
// Returns all known TPKs.
|
|
static PEP_STATUS tpk_all(PEP_SESSION, int, pgp_tpk_t **, int *) __attribute__((nonnull));
|
|
static PEP_STATUS tpk_all(PEP_SESSION session, int private_only,
|
|
pgp_tpk_t **tpksp, int *tpks_countp) {
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
sqlite3_stmt *stmt = private_only ? session->sq_sql.tsk_all : session->sq_sql.tpk_all;
|
|
status = key_loadn(session, stmt, tpksp, tpks_countp);
|
|
ERROR_OUT(NULL, status, "loading TPKs");
|
|
out:
|
|
sqlite3_reset(stmt);
|
|
return status;
|
|
}
|
|
|
|
// Returns keys that have a user id that matches the specified pattern.
|
|
//
|
|
// The keys returned must be freed using pgp_tpk_free.
|
|
static PEP_STATUS tpk_find_by_email(PEP_SESSION, const char *, int, pgp_tpk_t **, int *)
|
|
__attribute__((nonnull));
|
|
static PEP_STATUS tpk_find_by_email(PEP_SESSION session,
|
|
const char *pattern, int private_only,
|
|
pgp_tpk_t **tpksp, int *countp)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
T("(%s)", pattern);
|
|
|
|
sqlite3_stmt *stmt
|
|
= private_only ? session->sq_sql.tsk_find_by_email : session->sq_sql.tpk_find_by_email;
|
|
sqlite3_bind_text(stmt, 1, pattern, -1, SQLITE_STATIC);
|
|
|
|
status = key_loadn(session, stmt, tpksp, countp);
|
|
ERROR_OUT(NULL, status, "Searching for '%s'", pattern);
|
|
|
|
out:
|
|
sqlite3_reset(stmt);
|
|
T("(%s) -> %s (%d results)", pattern, pEp_status_to_string(status), *countp);
|
|
return status;
|
|
}
|
|
|
|
|
|
// Saves the specified TPK.
|
|
//
|
|
// This function takes ownership of TPK.
|
|
static PEP_STATUS tpk_save(PEP_SESSION, pgp_tpk_t, identity_list **)
|
|
__attribute__((nonnull(1, 2)));
|
|
static PEP_STATUS tpk_save(PEP_SESSION session, pgp_tpk_t tpk,
|
|
identity_list **private_idents)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_error_t err = NULL;
|
|
pgp_fingerprint_t pgp_fpr = NULL;
|
|
char *fpr = NULL;
|
|
void *tsk_buffer = NULL;
|
|
size_t tsk_buffer_len = 0;
|
|
int tried_commit = 0;
|
|
pgp_tpk_key_iter_t key_iter = NULL;
|
|
pgp_user_id_binding_iter_t user_id_iter = NULL;
|
|
char *email = NULL;
|
|
char *name = NULL;
|
|
|
|
sqlite3_stmt *stmt = session->sq_sql.begin_transaction;
|
|
int sqlite_result = sqlite3_step(stmt);
|
|
sqlite3_reset(stmt);
|
|
if (sqlite_result != SQLITE_DONE)
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR,
|
|
"begin transaction failed: %s",
|
|
sqlite3_errmsg(session->key_db));
|
|
|
|
pgp_fpr = pgp_tpk_fingerprint(tpk);
|
|
fpr = pgp_fingerprint_to_hex(pgp_fpr);
|
|
T("(%s, private_idents: %s)", fpr, private_idents ? "yes" : "no");
|
|
|
|
// Merge any existing data into TPK.
|
|
pgp_tpk_t current = NULL;
|
|
status = tpk_find(session, pgp_fpr, false, ¤t, NULL);
|
|
if (status == PEP_KEY_NOT_FOUND)
|
|
status = PEP_STATUS_OK;
|
|
else
|
|
ERROR_OUT(NULL, status, "Looking up %s", fpr);
|
|
if (current) {
|
|
tpk = pgp_tpk_merge(&err, tpk, current);
|
|
if (! tpk)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Merging TPKs");
|
|
}
|
|
|
|
int is_tsk = pgp_tpk_is_tsk(tpk);
|
|
|
|
// Serialize it.
|
|
pgp_writer_t writer = pgp_writer_alloc(&tsk_buffer, &tsk_buffer_len);
|
|
if (! writer)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
|
|
|
|
pgp_status_t pgp_status;
|
|
pgp_tsk_t tsk = pgp_tpk_as_tsk(tpk);
|
|
pgp_status = pgp_tsk_serialize(&err, tsk, writer);
|
|
pgp_tsk_free(tsk);
|
|
//pgp_writer_free(writer);
|
|
if (pgp_status != 0)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Serializing TPK");
|
|
|
|
|
|
// Insert the TSK into the DB.
|
|
stmt = session->sq_sql.tpk_save_insert_primary;
|
|
sqlite3_bind_text(stmt, 1, fpr, -1, SQLITE_STATIC);
|
|
sqlite3_bind_int(stmt, 2, is_tsk);
|
|
sqlite3_bind_blob(stmt, 3, tsk_buffer, tsk_buffer_len, SQLITE_STATIC);
|
|
|
|
sqlite_result = sqlite3_step(stmt);
|
|
sqlite3_reset(stmt);
|
|
if (sqlite_result != SQLITE_DONE)
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR,
|
|
"Saving TPK: %s", sqlite3_errmsg(session->key_db));
|
|
|
|
// Insert the "subkeys" (the primary key and the subkeys).
|
|
stmt = session->sq_sql.tpk_save_insert_subkeys;
|
|
// This inserts all of the keys in the TPK, i.e., revoked and
|
|
// expired keys, which is what we want.
|
|
key_iter = pgp_tpk_key_iter_all(tpk);
|
|
pgp_key_t key;
|
|
while ((key = pgp_tpk_key_iter_next(key_iter, NULL, NULL))) {
|
|
pgp_keyid_t keyid = pgp_key_keyid(key);
|
|
char *keyid_hex = pgp_keyid_to_hex(keyid);
|
|
sqlite3_bind_text(stmt, 1, keyid_hex, -1, SQLITE_STATIC);
|
|
sqlite3_bind_text(stmt, 2, fpr, -1, SQLITE_STATIC);
|
|
|
|
sqlite_result = sqlite3_step(stmt);
|
|
sqlite3_reset(stmt);
|
|
free(keyid_hex);
|
|
pgp_keyid_free(keyid);
|
|
if (sqlite_result != SQLITE_DONE) {
|
|
pgp_tpk_key_iter_free(key_iter);
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR,
|
|
"Updating subkeys: %s", sqlite3_errmsg(session->key_db));
|
|
}
|
|
}
|
|
pgp_tpk_key_iter_free(key_iter);
|
|
key_iter = NULL;
|
|
|
|
// Insert the "userids".
|
|
stmt = session->sq_sql.tpk_save_insert_userids;
|
|
user_id_iter = pgp_tpk_user_id_binding_iter(tpk);
|
|
pgp_user_id_binding_t binding;
|
|
int first = 1;
|
|
while ((binding = pgp_user_id_binding_iter_next(user_id_iter))) {
|
|
char *user_id_value = pgp_user_id_binding_user_id(binding);
|
|
if (!user_id_value || !*user_id_value)
|
|
continue;
|
|
|
|
// Ignore bindings with a self-revocation certificate, but no
|
|
// self-signature.
|
|
if (!pgp_user_id_binding_selfsig(binding)) {
|
|
free(user_id_value);
|
|
continue;
|
|
}
|
|
|
|
free(name);
|
|
name = NULL;
|
|
free(email);
|
|
email = NULL;
|
|
|
|
pgp_packet_t userid = pgp_user_id_new (user_id_value);
|
|
pgp_user_id_name(NULL, userid, &name);
|
|
pgp_user_id_address(NULL, userid, &email);
|
|
pgp_packet_free(userid);
|
|
free(user_id_value);
|
|
|
|
if (email) {
|
|
T(" userid: %s", email);
|
|
|
|
sqlite3_bind_text(stmt, 1, email, -1, SQLITE_STATIC);
|
|
sqlite3_bind_text(stmt, 2, fpr, -1, SQLITE_STATIC);
|
|
|
|
sqlite_result = sqlite3_step(stmt);
|
|
sqlite3_reset(stmt);
|
|
|
|
if (sqlite_result != SQLITE_DONE) {
|
|
pgp_user_id_binding_iter_free(user_id_iter);
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR,
|
|
"Updating userids: %s", sqlite3_errmsg(session->key_db));
|
|
}
|
|
}
|
|
|
|
if (first && private_idents && is_tsk) {
|
|
first = 0;
|
|
|
|
// Create an identity for the primary user id.
|
|
pEp_identity *ident = new_identity(email, fpr, NULL, name);
|
|
if (ident == NULL)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "new_identity");
|
|
|
|
*private_idents = identity_list_add(*private_idents, ident);
|
|
if (*private_idents == NULL)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "identity_list_add");
|
|
}
|
|
|
|
}
|
|
pgp_user_id_binding_iter_free(user_id_iter);
|
|
user_id_iter = NULL;
|
|
|
|
out:
|
|
// Prevent ERROR_OUT from causing an infinite loop.
|
|
if (! tried_commit) {
|
|
tried_commit = 1;
|
|
stmt = status == PEP_STATUS_OK
|
|
? session->sq_sql.commit_transaction
|
|
: session->sq_sql.rollback_transaction;
|
|
int sqlite_result = sqlite3_step(stmt);
|
|
sqlite3_reset(stmt);
|
|
if (sqlite_result != SQLITE_DONE)
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR,
|
|
status == PEP_STATUS_OK
|
|
? "commit failed: %s" : "rollback failed: %s",
|
|
sqlite3_errmsg(session->key_db));
|
|
}
|
|
|
|
T("(%s) -> %s", fpr, pEp_status_to_string(status));
|
|
|
|
free(email);
|
|
free(name);
|
|
if (user_id_iter)
|
|
pgp_user_id_binding_iter_free(user_id_iter);
|
|
if (key_iter)
|
|
pgp_tpk_key_iter_free(key_iter);
|
|
if (stmt)
|
|
sqlite3_reset(stmt);
|
|
free(tsk_buffer);
|
|
if (tpk)
|
|
pgp_tpk_free(tpk);
|
|
free(fpr);
|
|
pgp_fingerprint_free(pgp_fpr);
|
|
|
|
return status;
|
|
}
|
|
|
|
struct decrypt_cookie {
|
|
PEP_SESSION session;
|
|
int get_secret_keys_called;
|
|
stringlist_t *recipient_keylist;
|
|
stringlist_t *signer_keylist;
|
|
int good_checksums;
|
|
int missing_keys;
|
|
int bad_checksums;
|
|
int decrypted;
|
|
};
|
|
|
|
static pgp_status_t
|
|
get_public_keys_cb(void *cookie_raw,
|
|
pgp_keyid_t *keyids, size_t keyids_len,
|
|
pgp_tpk_t **tpks, size_t *tpk_len,
|
|
void (**our_free)(void *))
|
|
{
|
|
struct decrypt_cookie *cookie = cookie_raw;
|
|
PEP_SESSION session = cookie->session;
|
|
|
|
*tpks = calloc(keyids_len, sizeof(*tpks));
|
|
if (!*tpks)
|
|
return PGP_STATUS_UNKNOWN_ERROR;
|
|
*our_free = free;
|
|
|
|
int i, j;
|
|
j = 0;
|
|
for (i = 0; i < keyids_len; i ++) {
|
|
pgp_tpk_t tpk = NULL;
|
|
PEP_STATUS status
|
|
= tpk_find_by_keyid(session, keyids[i], false, &tpk, NULL);
|
|
if (status == PEP_STATUS_OK)
|
|
(*tpks)[j ++] = tpk;
|
|
}
|
|
*tpk_len = j;
|
|
return PGP_STATUS_SUCCESS;
|
|
}
|
|
|
|
static pgp_status_t
|
|
decrypt_cb(void *cookie_opaque,
|
|
pgp_pkesk_t *pkesks, size_t pkesk_count,
|
|
pgp_skesk_t *skesks, size_t skesk_count,
|
|
pgp_decryptor_do_decrypt_cb_t *decrypt,
|
|
void *decrypt_cookie,
|
|
pgp_fingerprint_t *identity_out)
|
|
{
|
|
pgp_error_t err = NULL;
|
|
struct decrypt_cookie *cookie = cookie_opaque;
|
|
PEP_SESSION session = cookie->session;
|
|
pgp_tpk_t *tsks = NULL;
|
|
int tsks_count = 0;
|
|
int wildcards = 0;
|
|
|
|
if (cookie->get_secret_keys_called)
|
|
// Prevent iterations, which isn't needed since we don't
|
|
// support SKESKs.
|
|
return PGP_STATUS_UNKNOWN_ERROR;
|
|
cookie->get_secret_keys_called = 1;
|
|
|
|
T("%zd PKESKs", pkesk_count);
|
|
|
|
for (int i = 0; i < pkesk_count; i ++) {
|
|
pgp_pkesk_t pkesk = pkesks[i];
|
|
pgp_keyid_t keyid = pgp_pkesk_recipient(pkesk); /* Reference. */
|
|
char *keyid_str = pgp_keyid_to_hex(keyid);
|
|
pgp_tpk_key_iter_t key_iter = NULL;
|
|
pgp_session_key_t sk = NULL;
|
|
|
|
T("Considering PKESK for %s", keyid_str);
|
|
|
|
if (strcmp(keyid_str, "0000000000000000") == 0) {
|
|
// Initially ignore wildcards.
|
|
wildcards = 1;
|
|
goto eol;
|
|
}
|
|
|
|
// Collect the recipients. Note: we must return the primary
|
|
// key's fingerprint.
|
|
pgp_tpk_t tpk = NULL;
|
|
int is_tsk = 0;
|
|
if (tpk_find_by_keyid(session, keyid, false, &tpk, &is_tsk) != PEP_STATUS_OK)
|
|
goto eol;
|
|
|
|
pgp_fingerprint_t fp = pgp_tpk_fingerprint(tpk);
|
|
char *fp_string = pgp_fingerprint_to_hex(fp);
|
|
stringlist_add_unique(cookie->recipient_keylist, fp_string);
|
|
free(fp_string);
|
|
pgp_fingerprint_free(fp);
|
|
|
|
if (cookie->decrypted)
|
|
goto eol;
|
|
|
|
// See if we have the secret key.
|
|
assert(is_tsk == pgp_tpk_is_tsk(tpk));
|
|
if (! is_tsk)
|
|
goto eol;
|
|
|
|
key_iter = pgp_tpk_key_iter_all(tpk);
|
|
pgp_key_t key;
|
|
while ((key = pgp_tpk_key_iter_next(key_iter, NULL, NULL))) {
|
|
pgp_keyid_t this_keyid = pgp_key_keyid(key);
|
|
char *this_keyid_hex = pgp_keyid_to_hex(this_keyid);
|
|
pgp_keyid_free(this_keyid);
|
|
|
|
int match = strcmp(keyid_str, this_keyid_hex) == 0;
|
|
free(this_keyid_hex);
|
|
if (match)
|
|
break;
|
|
}
|
|
|
|
if (key == NULL) {
|
|
assert(!"Inconsistent DB: key doesn't contain a subkey with keyid!");
|
|
goto eol;
|
|
}
|
|
|
|
uint8_t algo;
|
|
uint8_t session_key[1024];
|
|
size_t session_key_len = sizeof(session_key);
|
|
if (pgp_pkesk_decrypt(&err, pkesk, key, &algo,
|
|
session_key, &session_key_len) != 0) {
|
|
DUMP_ERR(err, PEP_UNKNOWN_ERROR, "pgp_pkesk_decrypt");
|
|
goto eol;
|
|
}
|
|
|
|
sk = pgp_session_key_from_bytes (session_key, session_key_len);
|
|
pgp_status_t status;
|
|
if ((status = decrypt (decrypt_cookie, algo, sk))) {
|
|
DUMP_STATUS(status, PEP_UNKNOWN_ERROR, "decrypt_cb");
|
|
goto eol;
|
|
}
|
|
|
|
T("Decrypted PKESK for %s", keyid_str);
|
|
|
|
*identity_out = pgp_tpk_fingerprint(tpk);
|
|
cookie->decrypted = 1;
|
|
|
|
eol:
|
|
if (sk)
|
|
pgp_session_key_free (sk);
|
|
free(keyid_str);
|
|
if (key_iter)
|
|
pgp_tpk_key_iter_free(key_iter);
|
|
if (tpk)
|
|
pgp_tpk_free(tpk);
|
|
}
|
|
|
|
// Consider wildcard recipients.
|
|
if (wildcards) for (int i = 0; i < pkesk_count && !cookie->decrypted; i ++) {
|
|
pgp_pkesk_t pkesk = pkesks[i];
|
|
pgp_keyid_t keyid = pgp_pkesk_recipient(pkesk); /* Reference. */
|
|
char *keyid_str = pgp_keyid_to_hex(keyid);
|
|
pgp_tpk_key_iter_t key_iter = NULL;
|
|
pgp_session_key_t sk = NULL;
|
|
|
|
if (strcmp(keyid_str, "0000000000000000") != 0)
|
|
goto eol2;
|
|
|
|
if (!tsks) {
|
|
if (tpk_all(session, true, &tsks, &tsks_count) != PEP_STATUS_OK) {
|
|
DUMP_ERR(NULL, PEP_UNKNOWN_ERROR, "Getting all tsks");
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < tsks_count; j ++) {
|
|
pgp_tpk_t tsk = tsks[j];
|
|
|
|
key_iter = pgp_tpk_key_iter_all(tsk);
|
|
pgp_key_t key;
|
|
pgp_signature_t selfsig;
|
|
while ((key = pgp_tpk_key_iter_next(key_iter, &selfsig, NULL))) {
|
|
if (! (pgp_signature_can_encrypt_at_rest(selfsig)
|
|
|| pgp_signature_can_encrypt_for_transport(selfsig)))
|
|
continue;
|
|
|
|
fprintf(stderr, "key: %s\n", pgp_key_debug(key));
|
|
|
|
// Note: for decryption to appear to succeed, we must
|
|
// get a valid algorithm (8 of 256 values) and a
|
|
// 16-bit checksum must match. Thus, we have about a
|
|
// 1 in 2**21 chance of having a false positive here.
|
|
uint8_t algo;
|
|
uint8_t session_key[1024];
|
|
size_t session_key_len = sizeof(session_key);
|
|
if (pgp_pkesk_decrypt(&err, pkesk, key,
|
|
&algo, session_key, &session_key_len)) {
|
|
pgp_error_free(err);
|
|
err = NULL;
|
|
continue;
|
|
}
|
|
|
|
// Add it to the recipient list.
|
|
pgp_fingerprint_t fp = pgp_tpk_fingerprint(tsk);
|
|
char *fp_string = pgp_fingerprint_to_hex(fp);
|
|
T("wildcard recipient appears to be %s", fp_string);
|
|
stringlist_add_unique(cookie->recipient_keylist, fp_string);
|
|
free(fp_string);
|
|
pgp_fingerprint_free(fp);
|
|
|
|
pgp_session_key_t sk = pgp_session_key_from_bytes (session_key,
|
|
session_key_len);
|
|
pgp_status_t status;
|
|
if ((status = decrypt (decrypt_cookie, algo, sk))) {
|
|
DUMP_STATUS(status, PEP_UNKNOWN_ERROR, "decrypt_cb");
|
|
goto eol2;
|
|
}
|
|
|
|
*identity_out = pgp_tpk_fingerprint(tsk);
|
|
cookie->decrypted = 1;
|
|
|
|
break;
|
|
}
|
|
|
|
pgp_tpk_key_iter_free(key_iter);
|
|
key_iter = NULL;
|
|
}
|
|
eol2:
|
|
if (sk)
|
|
pgp_session_key_free (sk);
|
|
free(keyid_str);
|
|
if (key_iter)
|
|
pgp_tpk_key_iter_free(key_iter);
|
|
}
|
|
|
|
if (tsks) {
|
|
for (int i = 0; i < tsks_count; i ++)
|
|
pgp_tpk_free(tsks[i]);
|
|
free(tsks);
|
|
}
|
|
|
|
return cookie->decrypted ? PGP_STATUS_SUCCESS : PGP_STATUS_UNKNOWN_ERROR;
|
|
}
|
|
|
|
static pgp_status_t
|
|
check_signatures_cb(void *cookie_opaque, pgp_message_structure_t structure)
|
|
{
|
|
struct decrypt_cookie *cookie = cookie_opaque;
|
|
PEP_SESSION session = cookie->session;
|
|
|
|
pgp_message_structure_iter_t iter
|
|
= pgp_message_structure_iter (structure);
|
|
for (pgp_message_layer_t layer = pgp_message_structure_iter_next (iter);
|
|
layer;
|
|
layer = pgp_message_structure_iter_next (iter)) {
|
|
pgp_verification_result_iter_t results;
|
|
|
|
switch (pgp_message_layer_variant (layer)) {
|
|
case PGP_MESSAGE_LAYER_COMPRESSION:
|
|
case PGP_MESSAGE_LAYER_ENCRYPTION:
|
|
break;
|
|
|
|
case PGP_MESSAGE_LAYER_SIGNATURE_GROUP:
|
|
pgp_message_layer_signature_group(layer, &results);
|
|
pgp_verification_result_t result;
|
|
while ((result = pgp_verification_result_iter_next (results))) {
|
|
pgp_signature_t sig;
|
|
pgp_keyid_t keyid = NULL;
|
|
char *keyid_str = NULL;
|
|
|
|
switch (pgp_verification_result_variant (result)) {
|
|
case PGP_VERIFICATION_RESULT_GOOD_CHECKSUM:
|
|
// We need to add the fingerprint of the primary
|
|
// key to cookie->signer_keylist.
|
|
|
|
pgp_verification_result_good_checksum (result, &sig, NULL,
|
|
NULL, NULL, NULL);
|
|
|
|
// First try looking up by the TPK using the
|
|
// IssuerFingerprint subpacket.
|
|
pgp_fingerprint_t fpr
|
|
= pgp_signature_issuer_fingerprint(sig);
|
|
if (fpr) {
|
|
// Even though we have a fingerprint, we have
|
|
// to look the key up by keyid, because we
|
|
// want to match on subkeys and we only store
|
|
// keyids for subkeys.
|
|
keyid = pgp_fingerprint_to_keyid(fpr);
|
|
pgp_fingerprint_free(fpr);
|
|
} else {
|
|
// That is not available, try using the Issuer
|
|
// subpacket.
|
|
keyid = pgp_signature_issuer(sig);
|
|
}
|
|
|
|
if (! keyid) {
|
|
T("signature with no Issuer or Issuer Fingerprint subpacket!");
|
|
goto eol;
|
|
}
|
|
|
|
pgp_tpk_t tpk;
|
|
if (tpk_find_by_keyid(session, keyid, false,
|
|
&tpk, NULL) != PEP_STATUS_OK)
|
|
; // Soft error. Ignore.
|
|
|
|
if (tpk) {
|
|
// Ok, we have a TPK.
|
|
|
|
// We need the primary key's fingerprint (not
|
|
// the issuer fingerprint).
|
|
pgp_fingerprint_t primary_fpr
|
|
= pgp_tpk_fingerprint(tpk);
|
|
char *primary_fpr_str
|
|
= pgp_fingerprint_to_hex(primary_fpr);
|
|
stringlist_add_unique(cookie->signer_keylist,
|
|
primary_fpr_str);
|
|
|
|
T("Good signature from %s", primary_fpr_str);
|
|
|
|
// XXX: Check that the TPK and the key used to make
|
|
// the signature and the signature itself are alive
|
|
// and not revoked. Revoked =>
|
|
// PEP_DECRYPT_SIGNATURE_DOES_NOT_MATCH; Expired key
|
|
// or sig => PEP_DECRYPTED.
|
|
cookie->good_checksums ++;
|
|
|
|
free(primary_fpr_str);
|
|
pgp_fingerprint_free(primary_fpr);
|
|
pgp_tpk_free(tpk);
|
|
} else {
|
|
// If we get
|
|
// PGP_VERIFICATION_RESULT_CODE_GOOD_CHECKSUM, then the
|
|
// TPK should be available. But, another process
|
|
// could have deleted the key from the store in the
|
|
// mean time, so be tolerant.
|
|
T("Key to check signature from %s disappeared",
|
|
keyid_str);
|
|
cookie->missing_keys ++;
|
|
}
|
|
break;
|
|
|
|
case PGP_VERIFICATION_RESULT_MISSING_KEY:
|
|
pgp_verification_result_missing_key (result, &sig);
|
|
keyid = pgp_signature_issuer (sig);
|
|
keyid_str = pgp_keyid_to_string (keyid);
|
|
T("No key to check signature from %s", keyid_str);
|
|
|
|
cookie->missing_keys ++;
|
|
break;
|
|
|
|
case PGP_VERIFICATION_RESULT_BAD_CHECKSUM:
|
|
pgp_verification_result_bad_checksum (result, &sig);
|
|
keyid = pgp_signature_issuer (sig);
|
|
if (keyid) {
|
|
keyid_str = pgp_keyid_to_string (keyid);
|
|
T("Bad signature from %s", keyid_str);
|
|
} else {
|
|
T("Bad signature without issuer information");
|
|
}
|
|
|
|
cookie->bad_checksums ++;
|
|
break;
|
|
|
|
default:
|
|
assert (! "reachable");
|
|
}
|
|
|
|
eol:
|
|
free (keyid_str);
|
|
pgp_signature_free (sig);
|
|
pgp_verification_result_free (result);
|
|
}
|
|
pgp_verification_result_iter_free (results);
|
|
break;
|
|
|
|
default:
|
|
assert (! "reachable");
|
|
}
|
|
|
|
pgp_message_layer_free (layer);
|
|
}
|
|
|
|
pgp_message_structure_iter_free (iter);
|
|
pgp_message_structure_free (structure);
|
|
|
|
return PGP_STATUS_SUCCESS;
|
|
}
|
|
|
|
PEP_STATUS pgp_decrypt_and_verify(
|
|
PEP_SESSION session, const char *ctext, size_t csize,
|
|
const char *dsigtext, size_t dsigsize,
|
|
char **ptext, size_t *psize, stringlist_t **keylist,
|
|
char** filename_ptr)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
struct decrypt_cookie cookie = { session, 0, NULL, NULL, 0, 0, 0, };
|
|
pgp_reader_t reader = NULL;
|
|
pgp_writer_t writer = NULL;
|
|
pgp_reader_t decryptor = NULL;
|
|
*ptext = NULL;
|
|
*psize = 0;
|
|
|
|
// XXX: We don't yet handle detached signatures over encrypted
|
|
// messages.
|
|
assert(!dsigtext);
|
|
|
|
cookie.recipient_keylist = new_stringlist(NULL);
|
|
if (!cookie.recipient_keylist)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "recipient_keylist");
|
|
|
|
cookie.signer_keylist = new_stringlist(NULL);
|
|
if (!cookie.signer_keylist)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "signer_keylist");
|
|
|
|
reader = pgp_reader_from_bytes((const uint8_t *) ctext, csize);
|
|
if (! reader)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "Creating reader");
|
|
|
|
writer = pgp_writer_alloc((void **) ptext, psize);
|
|
if (! writer)
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR, "Creating writer");
|
|
|
|
pgp_error_t err = NULL;
|
|
decryptor = pgp_decryptor_new(&err, reader,
|
|
get_public_keys_cb, decrypt_cb,
|
|
check_signatures_cb, &cookie, 0);
|
|
if (! decryptor)
|
|
ERROR_OUT(err, PEP_DECRYPT_NO_KEY, "pgp_decryptor_new");
|
|
|
|
// Copy 128 MB at a time.
|
|
ssize_t nread;
|
|
while ((nread = pgp_reader_copy (&err, decryptor, writer,
|
|
128 * 1024 * 1024) > 0))
|
|
;
|
|
if (nread < 0)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "pgp_reader_read");
|
|
|
|
// Add a terminating NUL for naive users
|
|
pgp_writer_write(&err, writer, (const uint8_t *) &""[0], 1);
|
|
|
|
if (! cookie.decrypted)
|
|
ERROR_OUT(err, PEP_DECRYPT_NO_KEY, "Decryption failed");
|
|
|
|
if (! cookie.signer_keylist) {
|
|
cookie.signer_keylist = new_stringlist("");
|
|
if (! cookie.signer_keylist)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "cookie.signer_keylist");
|
|
}
|
|
if (!cookie.signer_keylist->value)
|
|
stringlist_add(cookie.signer_keylist, "");
|
|
|
|
*keylist = cookie.signer_keylist;
|
|
stringlist_append(*keylist, cookie.recipient_keylist);
|
|
|
|
out:
|
|
if (status == PEP_STATUS_OK) {
|
|
if (cookie.bad_checksums) {
|
|
// If there are any bad signatures, fail.
|
|
status = PEP_DECRYPT_SIGNATURE_DOES_NOT_MATCH;
|
|
} else if (cookie.good_checksums) {
|
|
// If there is at least one signature that we can verify,
|
|
// succeed.
|
|
status = PEP_DECRYPTED_AND_VERIFIED;
|
|
} else {
|
|
// We couldn't verify any signatures (possibly because we
|
|
// don't have the keys).
|
|
status = PEP_DECRYPTED;
|
|
}
|
|
} else {
|
|
free_stringlist(cookie.recipient_keylist);
|
|
free_stringlist(cookie.signer_keylist);
|
|
free(*ptext);
|
|
}
|
|
|
|
if (reader)
|
|
pgp_reader_free(reader);
|
|
if (decryptor)
|
|
pgp_reader_free(decryptor);
|
|
if (writer)
|
|
pgp_writer_free(writer);
|
|
|
|
T("-> %s", pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
PEP_STATUS pgp_verify_text(
|
|
PEP_SESSION session, const char *text, size_t size,
|
|
const char *signature, size_t sig_size, stringlist_t **keylist)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_error_t err = NULL;
|
|
struct decrypt_cookie cookie = { session, 0, NULL, NULL, 0, 0, 0, };
|
|
pgp_reader_t reader = NULL;
|
|
pgp_reader_t dsig_reader = NULL;
|
|
pgp_reader_t verifier = NULL;
|
|
|
|
if (size == 0 || sig_size == 0)
|
|
return PEP_DECRYPT_WRONG_FORMAT;
|
|
|
|
cookie.recipient_keylist = new_stringlist(NULL);
|
|
if (!cookie.recipient_keylist)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
|
|
|
|
cookie.signer_keylist = new_stringlist(NULL);
|
|
if (!cookie.signer_keylist)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
|
|
|
|
reader = pgp_reader_from_bytes((const uint8_t *) text, size);
|
|
if (! reader)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "Creating reader");
|
|
|
|
dsig_reader = NULL;
|
|
if (signature) {
|
|
dsig_reader = pgp_reader_from_bytes((uint8_t *) signature, sig_size);
|
|
if (! dsig_reader)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "Creating signature reader");
|
|
}
|
|
|
|
if (dsig_reader)
|
|
verifier = pgp_detached_verifier_new(&err, dsig_reader, reader,
|
|
get_public_keys_cb,
|
|
check_signatures_cb,
|
|
&cookie, 0);
|
|
else
|
|
verifier = pgp_verifier_new(&err, reader,
|
|
get_public_keys_cb,
|
|
check_signatures_cb,
|
|
&cookie, 0);
|
|
if (! verifier)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Creating verifier");
|
|
if (pgp_reader_discard(&err, verifier) < 0)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "verifier");
|
|
|
|
if (! cookie.signer_keylist) {
|
|
cookie.signer_keylist = new_stringlist("");
|
|
if (! cookie.signer_keylist)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "cookie.signer_keylist");
|
|
}
|
|
if (!cookie.signer_keylist->value)
|
|
stringlist_add(cookie.signer_keylist, "");
|
|
|
|
*keylist = cookie.signer_keylist;
|
|
stringlist_append(*keylist, cookie.recipient_keylist);
|
|
|
|
out:
|
|
if (status == PEP_STATUS_OK) {
|
|
if (cookie.bad_checksums) {
|
|
// If there are any bad signatures, fail.
|
|
status = PEP_DECRYPT_SIGNATURE_DOES_NOT_MATCH;
|
|
} else if (cookie.good_checksums) {
|
|
// If there is at least one signature that we can verify,
|
|
// succeed.
|
|
status = PEP_VERIFIED;
|
|
} else {
|
|
// We couldn't verify any signatures (possibly because we
|
|
// don't have the keys).
|
|
status = PEP_UNENCRYPTED;
|
|
}
|
|
} else {
|
|
free_stringlist(cookie.recipient_keylist);
|
|
free_stringlist(cookie.signer_keylist);
|
|
}
|
|
|
|
if (verifier)
|
|
pgp_reader_free(verifier);
|
|
if (reader)
|
|
pgp_reader_free(reader);
|
|
if (dsig_reader)
|
|
pgp_reader_free(dsig_reader);
|
|
|
|
T("-> %s", pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
|
|
PEP_STATUS pgp_sign_only(
|
|
PEP_SESSION session, const char* fpr, const char *ptext,
|
|
size_t psize, char **stext, size_t *ssize)
|
|
{
|
|
assert(session);
|
|
assert(fpr && fpr[0]);
|
|
assert(ptext);
|
|
assert(psize);
|
|
assert(stext);
|
|
assert(ssize);
|
|
*stext = NULL;
|
|
*ssize = 0;
|
|
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_error_t err = NULL;
|
|
pgp_tpk_t signer_tpk = NULL;
|
|
pgp_tpk_key_iter_t iter = NULL;
|
|
pgp_key_pair_t signing_keypair = NULL;
|
|
pgp_signer_t signer = NULL;
|
|
pgp_writer_stack_t ws = NULL;
|
|
|
|
status = tpk_find_by_fpr_hex(session, fpr, true, &signer_tpk, NULL);
|
|
ERROR_OUT(NULL, status, "Looking up key '%s'", fpr);
|
|
|
|
iter = pgp_tpk_key_iter_valid(signer_tpk);
|
|
pgp_tpk_key_iter_signing_capable (iter);
|
|
pgp_tpk_key_iter_unencrypted_secret (iter, true);
|
|
|
|
// If there are multiple signing capable subkeys, we just take
|
|
// the first one, whichever one that happens to be.
|
|
pgp_key_t key = pgp_tpk_key_iter_next (iter, NULL, NULL);
|
|
if (! key)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR,
|
|
"%s has no signing capable key", fpr);
|
|
|
|
signing_keypair = pgp_key_into_key_pair (NULL, pgp_key_clone (key));
|
|
if (! signing_keypair)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "Creating a keypair");
|
|
|
|
signer = pgp_key_pair_as_signer (signing_keypair);
|
|
if (! signer)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "Creating a signer");
|
|
|
|
|
|
pgp_writer_t writer = pgp_writer_alloc((void **) stext, ssize);
|
|
writer = pgp_armor_writer_new(&err, writer,
|
|
PGP_ARMOR_KIND_MESSAGE, NULL, 0);
|
|
if (!writer)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Setting up armor writer");
|
|
|
|
ws = pgp_writer_stack_message(writer);
|
|
|
|
ws = pgp_signer_new_detached(&err, ws, &signer, 1, 0);
|
|
if (!ws)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Setting up signer");
|
|
|
|
pgp_status_t write_status =
|
|
pgp_writer_stack_write_all (&err, ws,
|
|
(uint8_t *) ptext, psize);
|
|
if (write_status != 0)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Encrypting message");
|
|
|
|
// Add a terminating NUL for naive users
|
|
void *t = realloc(*stext, *ssize + 1);
|
|
if (! t)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
|
|
*stext = t;
|
|
(*stext)[*ssize] = 0;
|
|
|
|
out:
|
|
if (ws) {
|
|
pgp_status_t pgp_status = pgp_writer_stack_finalize (&err, ws);
|
|
ws = NULL;
|
|
if (pgp_status != 0)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Flushing writer");
|
|
}
|
|
|
|
if (signer)
|
|
pgp_signer_free (signer);
|
|
if (signing_keypair)
|
|
pgp_key_pair_free (signing_keypair);
|
|
if (iter)
|
|
pgp_tpk_key_iter_free (iter);
|
|
if (signer_tpk)
|
|
pgp_tpk_free(signer_tpk);
|
|
|
|
T("(%s)-> %s", fpr, pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
static PEP_STATUS pgp_encrypt_sign_optional(
|
|
PEP_SESSION session, const stringlist_t *keylist, const char *ptext,
|
|
size_t psize, char **ctext, size_t *csize, bool sign)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_error_t err = NULL;
|
|
int keys_count = 0;
|
|
pgp_tpk_t *keys = NULL;
|
|
pgp_tpk_t signer_tpk = NULL;
|
|
pgp_writer_stack_t ws = NULL;
|
|
pgp_tpk_key_iter_t iter = NULL;
|
|
pgp_key_pair_t signing_keypair = NULL;
|
|
pgp_signer_t signer = NULL;
|
|
|
|
assert(session);
|
|
assert(keylist);
|
|
assert(ptext);
|
|
assert(psize);
|
|
assert(ctext);
|
|
assert(csize);
|
|
|
|
*ctext = NULL;
|
|
*csize = 0;
|
|
|
|
keys = calloc(stringlist_length(keylist), sizeof(*keys));
|
|
if (keys == NULL)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
|
|
|
|
// Get the keys for the recipients.
|
|
const stringlist_t *_keylist;
|
|
for (_keylist = keylist; _keylist != NULL; _keylist = _keylist->next) {
|
|
assert(_keylist->value);
|
|
pgp_fingerprint_t pgp_fpr = pgp_fingerprint_from_hex(_keylist->value);
|
|
status = tpk_find_by_fpr(session, pgp_fpr, false, &keys[keys_count ++], NULL);
|
|
pgp_fingerprint_free(pgp_fpr);
|
|
ERROR_OUT(NULL, status, "Looking up key for recipient '%s'", _keylist->value);
|
|
}
|
|
|
|
if (sign) {
|
|
// The first key in the keylist is the signer.
|
|
status = tpk_find_by_fpr_hex(session, keylist->value, true, &signer_tpk, NULL);
|
|
ERROR_OUT(NULL, status, "Looking up key for signing '%s'", keylist->value);
|
|
}
|
|
|
|
pgp_writer_t writer = pgp_writer_alloc((void **) ctext, csize);
|
|
writer = pgp_armor_writer_new(&err, writer,
|
|
PGP_ARMOR_KIND_MESSAGE, NULL, 0);
|
|
if (!writer)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Setting up armor writer");
|
|
|
|
ws = pgp_writer_stack_message(writer);
|
|
ws = pgp_encryptor_new (&err, ws,
|
|
NULL, 0, keys, keys_count,
|
|
PGP_ENCRYPTION_MODE_FOR_TRANSPORT, 0);
|
|
if (!ws)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Setting up encryptor");
|
|
|
|
if (sign) {
|
|
iter = pgp_tpk_key_iter_valid(signer_tpk);
|
|
pgp_tpk_key_iter_signing_capable (iter);
|
|
pgp_tpk_key_iter_unencrypted_secret (iter, true);
|
|
|
|
// If there are multiple signing capable subkeys, we just take
|
|
// the first one, whichever one that happens to be.
|
|
pgp_key_t key = pgp_tpk_key_iter_next (iter, NULL, NULL);
|
|
if (! key)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR,
|
|
"%s has no signing capable key", keylist->value);
|
|
|
|
signing_keypair = pgp_key_into_key_pair (NULL, pgp_key_clone (key));
|
|
if (! signing_keypair)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "Creating a keypair");
|
|
|
|
signer = pgp_key_pair_as_signer (signing_keypair);
|
|
if (! signer)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "Creating a signer");
|
|
|
|
ws = pgp_signer_new(&err, ws, &signer, 1, 0);
|
|
if (!ws)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Setting up signer");
|
|
}
|
|
|
|
ws = pgp_literal_writer_new (&err, ws);
|
|
if (!ws)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Setting up literal writer");
|
|
|
|
pgp_status_t write_status =
|
|
pgp_writer_stack_write_all (&err, ws,
|
|
(uint8_t *) ptext, psize);
|
|
if (write_status != 0)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Encrypting message");
|
|
|
|
// Add a terminating NUL for naive users
|
|
void *t = realloc(*ctext, *csize + 1);
|
|
if (! t)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
|
|
*ctext = t;
|
|
(*ctext)[*csize] = 0;
|
|
|
|
out:
|
|
if (ws) {
|
|
pgp_status_t pgp_status = pgp_writer_stack_finalize (&err, ws);
|
|
ws = NULL;
|
|
if (pgp_status != 0)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Flushing writer");
|
|
}
|
|
|
|
if (signer)
|
|
pgp_signer_free (signer);
|
|
if (signing_keypair)
|
|
pgp_key_pair_free (signing_keypair);
|
|
if (iter)
|
|
pgp_tpk_key_iter_free (iter);
|
|
if (signer_tpk)
|
|
pgp_tpk_free(signer_tpk);
|
|
|
|
for (int i = 0; i < keys_count; i ++)
|
|
pgp_tpk_free(keys[i]);
|
|
free(keys);
|
|
|
|
T("-> %s", pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
PEP_STATUS pgp_encrypt_only(
|
|
PEP_SESSION session, const stringlist_t *keylist, const char *ptext,
|
|
size_t psize, char **ctext, size_t *csize)
|
|
{
|
|
return pgp_encrypt_sign_optional(session, keylist, ptext,
|
|
psize, ctext, csize, false);
|
|
}
|
|
|
|
PEP_STATUS pgp_encrypt_and_sign(
|
|
PEP_SESSION session, const stringlist_t *keylist, const char *ptext,
|
|
size_t psize, char **ctext, size_t *csize)
|
|
{
|
|
return pgp_encrypt_sign_optional(session, keylist, ptext,
|
|
psize, ctext, csize, true);
|
|
}
|
|
|
|
|
|
PEP_STATUS pgp_generate_keypair(PEP_SESSION session, pEp_identity *identity)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_error_t err = NULL;
|
|
char *userid = NULL;
|
|
pgp_tpk_t tpk = NULL;
|
|
pgp_fingerprint_t pgp_fpr = NULL;
|
|
char *fpr = NULL;
|
|
|
|
assert(session);
|
|
assert(identity);
|
|
assert(identity->address);
|
|
assert(identity->fpr == NULL || identity->fpr[0] == 0);
|
|
assert(identity->username);
|
|
|
|
size_t userid_size = strlen(identity->username)+strlen(identity->address)+3+1;
|
|
userid = (char *) calloc(1, userid_size);
|
|
assert(userid);
|
|
if (!userid)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
|
|
|
|
int r = snprintf(userid, userid_size, "%s <%s>", identity->username, identity->address);
|
|
assert(r >= 0 && r < userid_size);
|
|
if (r < 0)
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR, "snprintf");
|
|
|
|
T("(%s)", userid);
|
|
|
|
// Generate a key.
|
|
pgp_tpk_builder_t tpkb = pgp_tpk_builder_general_purpose
|
|
(PGP_TPK_CIPHER_SUITE_RSA3K, userid);
|
|
pgp_signature_t rev;
|
|
if (pgp_tpk_builder_generate(&err, tpkb, &tpk, &rev))
|
|
ERROR_OUT(err, PEP_CANNOT_CREATE_KEY, "Generating a key pair");
|
|
|
|
// XXX: We should return this.
|
|
pgp_signature_free(rev);
|
|
|
|
// Get the fingerprint.
|
|
pgp_fpr = pgp_tpk_fingerprint(tpk);
|
|
fpr = pgp_fingerprint_to_hex(pgp_fpr);
|
|
|
|
status = tpk_save(session, tpk, NULL);
|
|
tpk = NULL;
|
|
if (status != 0)
|
|
ERROR_OUT(NULL, PEP_CANNOT_CREATE_KEY, "saving TSK");
|
|
|
|
free(identity->fpr);
|
|
identity->fpr = fpr;
|
|
fpr = NULL;
|
|
|
|
out:
|
|
if (pgp_fpr)
|
|
pgp_fingerprint_free(pgp_fpr);
|
|
free(fpr);
|
|
if (tpk)
|
|
pgp_tpk_free(tpk);
|
|
free(userid);
|
|
|
|
T("-> %s", pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
#define SQL_DELETE "DELETE FROM keys WHERE primary_key = '%s' ;"
|
|
static const char *sql_delete = SQL_DELETE;
|
|
static const size_t sql_delete_size = sizeof(SQL_DELETE);
|
|
|
|
// FIXME: this is deleting the key from the index but not the key data
|
|
|
|
PEP_STATUS pgp_delete_keypair(PEP_SESSION session, const char *fpr)
|
|
{
|
|
assert(session && fpr && fpr[0]);
|
|
if (!(session && fpr && fpr[0]))
|
|
return PEP_ILLEGAL_VALUE;
|
|
|
|
size_t sql_size = sql_delete_size + strlen(fpr);
|
|
char *sql = calloc(1, sql_size);
|
|
assert(sql);
|
|
if (!sql)
|
|
return PEP_OUT_OF_MEMORY;
|
|
|
|
int r = snprintf(sql, sql_size, sql_delete, fpr);
|
|
assert(r > 0 && r < sql_size);
|
|
if (r < 0)
|
|
return PEP_UNKNOWN_ERROR;
|
|
|
|
int sqlite_result = sqlite3_exec(session->key_db, sql, NULL, NULL, NULL);
|
|
assert(sqlite_result == SQLITE_OK);
|
|
if (sqlite_result != SQLITE_OK)
|
|
return PEP_CANNOT_DELETE_KEY;
|
|
|
|
sqlite_result = sqlite3_changes(session->key_db);
|
|
assert(sqlite_result >= 0 && sqlite_result < 2);
|
|
if (sqlite_result < 1)
|
|
return PEP_KEY_NOT_FOUND;
|
|
|
|
return PEP_STATUS_OK;
|
|
}
|
|
|
|
// XXX: This also needs to handle revocation certificates.
|
|
PEP_STATUS pgp_import_keydata(PEP_SESSION session, const char *key_data,
|
|
size_t size, identity_list **private_idents)
|
|
{
|
|
PEP_STATUS status = PEP_NO_KEY_IMPORTED;
|
|
pgp_error_t err;
|
|
pgp_tpk_parser_t parser = NULL;
|
|
|
|
if (private_idents)
|
|
*private_idents = NULL;
|
|
|
|
T("parsing %zd bytes", size);
|
|
|
|
pgp_packet_parser_result_t ppr
|
|
= pgp_packet_parser_from_bytes(&err, (uint8_t *) key_data, size);
|
|
if (! ppr)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Creating packet parser");
|
|
|
|
pgp_tag_t tag = pgp_packet_parser_result_tag(ppr);
|
|
switch (tag) {
|
|
case PGP_TAG_SIGNATURE: {
|
|
// The following asserts can't fail, because
|
|
// pgp_packet_parser_result_tag succeeded and the tag is
|
|
// right.
|
|
pgp_packet_parser_t pp = pgp_packet_parser_result_packet_parser (ppr);
|
|
assert(pp);
|
|
|
|
pgp_packet_t packet = NULL;
|
|
if (pgp_packet_parser_next(&err, pp, &packet, &ppr))
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Getting signature packet");
|
|
|
|
pgp_signature_t sig = pgp_packet_ref_signature (packet);
|
|
assert(sig);
|
|
|
|
pgp_tpk_t tpk = NULL;
|
|
|
|
pgp_fingerprint_t issuer_fpr = pgp_signature_issuer_fingerprint(sig);
|
|
if (issuer_fpr) {
|
|
char *issuer_fpr_hex = pgp_fingerprint_to_hex(issuer_fpr);
|
|
T("Importing a signature issued by %s", issuer_fpr_hex);
|
|
|
|
status = tpk_find_by_fpr_hex(session, issuer_fpr_hex,
|
|
false, &tpk, NULL);
|
|
if (status && status != PEP_KEY_NOT_FOUND)
|
|
DUMP_ERR(NULL, status, "Looking up %s", issuer_fpr_hex);
|
|
|
|
free(issuer_fpr_hex);
|
|
pgp_fingerprint_free(issuer_fpr);
|
|
}
|
|
|
|
if (! tpk) {
|
|
pgp_keyid_t issuer = pgp_signature_issuer(sig);
|
|
if (issuer) {
|
|
char *issuer_hex = pgp_keyid_to_hex(issuer);
|
|
T("Importing a signature issued by %s", issuer_hex);
|
|
|
|
status = tpk_find_by_keyid_hex(session, issuer_hex,
|
|
false, &tpk, NULL);
|
|
if (status && status != PEP_KEY_NOT_FOUND)
|
|
DUMP_ERR(NULL, status, "Looking up %s", issuer_hex);
|
|
|
|
free(issuer_hex);
|
|
pgp_keyid_free(issuer);
|
|
}
|
|
}
|
|
|
|
// We need a packet. sig is only a reference, so we just need
|
|
// to free it.
|
|
pgp_signature_free(sig);
|
|
|
|
if (tpk) {
|
|
T("Merging packet: %s", pgp_packet_debug(packet));
|
|
|
|
tpk = pgp_tpk_merge_packets (&err, tpk, &packet, 1);
|
|
if (! tpk)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Merging signature");
|
|
|
|
status = tpk_save(session, tpk, NULL);
|
|
if (status)
|
|
ERROR_OUT(NULL, status, "saving merged TPK");
|
|
status = PEP_KEY_IMPORTED;
|
|
}
|
|
break;
|
|
}
|
|
case PGP_TAG_PUBLIC_KEY:
|
|
case PGP_TAG_SECRET_KEY: {
|
|
parser = pgp_tpk_parser_from_packet_parser(ppr);
|
|
pgp_tpk_t tpk;
|
|
int count = 0;
|
|
err = NULL;
|
|
while ((tpk = pgp_tpk_parser_next(&err, parser))) {
|
|
count ++;
|
|
|
|
T("#%d. TPK for %s, %s",
|
|
count, pgp_tpk_primary_user_id(tpk),
|
|
pgp_fingerprint_to_hex(pgp_tpk_fingerprint(tpk)));
|
|
|
|
// If private_idents is not NULL and there is any private key
|
|
// material, it will be saved.
|
|
status = tpk_save(session, tpk, private_idents);
|
|
if (status == PEP_STATUS_OK)
|
|
status = PEP_KEY_IMPORTED;
|
|
else
|
|
ERROR_OUT(NULL, status, "saving TPK");
|
|
}
|
|
if (err || count == 0)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "parsing key data");
|
|
break;
|
|
}
|
|
default:
|
|
ERROR_OUT(NULL, PEP_NO_KEY_IMPORTED,
|
|
"Can't import %s", pgp_tag_to_string(tag));
|
|
break;
|
|
}
|
|
|
|
out:
|
|
if (parser)
|
|
pgp_tpk_parser_free(parser);
|
|
|
|
T("-> %s", pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
PEP_STATUS pgp_export_keydata(
|
|
PEP_SESSION session, const char *fpr, char **key_data, size_t *size,
|
|
bool secret)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_error_t err = NULL;
|
|
pgp_tpk_t tpk = NULL;
|
|
pgp_writer_t armor_writer = NULL;
|
|
|
|
assert(session);
|
|
assert(fpr);
|
|
assert(key_data);
|
|
assert(*key_data == NULL);
|
|
assert(size);
|
|
|
|
*size = 0;
|
|
|
|
T("(%s, %s)", fpr, secret ? "secret" : "public");
|
|
|
|
// If the caller asks for a secret key and we only have a
|
|
// public key, then we return an error.
|
|
status = tpk_find_by_fpr_hex(session, fpr, secret, &tpk, NULL);
|
|
ERROR_OUT(NULL, status, "Looking up TSK for %s", fpr);
|
|
|
|
pgp_writer_t memory_writer = pgp_writer_alloc((void **) key_data, size);
|
|
if (! memory_writer)
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR, "creating memory writer");
|
|
armor_writer = pgp_armor_writer_new(&err, memory_writer,
|
|
PGP_ARMOR_KIND_PUBLICKEY, NULL, 0);
|
|
if (! armor_writer) {
|
|
pgp_writer_free(memory_writer);
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "creating armored writer");
|
|
}
|
|
|
|
if (secret) {
|
|
pgp_tsk_t tsk = pgp_tpk_as_tsk(tpk);
|
|
if (pgp_tsk_serialize(&err, tsk, armor_writer))
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "serializing TSK");
|
|
pgp_tsk_free(tsk);
|
|
} else {
|
|
if (pgp_tpk_serialize(&err, tpk, armor_writer))
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "serializing TPK");
|
|
}
|
|
|
|
out:
|
|
if (armor_writer)
|
|
pgp_writer_free(armor_writer);
|
|
|
|
if (tpk)
|
|
pgp_tpk_free(tpk);
|
|
|
|
T("(%s) -> %s", fpr, pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
char* _undot_address(const char* address) {
|
|
if (!address)
|
|
return NULL;
|
|
|
|
int addr_len = strlen(address);
|
|
const char* at = strstr(address, "@");
|
|
|
|
if (!at)
|
|
at = address + addr_len;
|
|
|
|
char* retval = calloc(1, addr_len + 1);
|
|
|
|
const char* addr_curr = address;
|
|
char* retval_curr = retval;
|
|
|
|
while (addr_curr < at) {
|
|
if (*addr_curr == '.') {
|
|
addr_curr++;
|
|
continue;
|
|
}
|
|
*retval_curr = *addr_curr;
|
|
retval_curr++;
|
|
addr_curr++;
|
|
}
|
|
if (*addr_curr == '@')
|
|
strcat(retval_curr, addr_curr);
|
|
|
|
return retval;
|
|
}
|
|
|
|
static stringpair_list_t *add_key(PEP_SESSION session,
|
|
stringpair_list_t *keyinfo_list,
|
|
stringlist_t* keylist,
|
|
pgp_tpk_t tpk, pgp_fingerprint_t fpr) {
|
|
bool revoked = false;
|
|
// Don't add revoked keys to the keyinfo_list.
|
|
if (keyinfo_list) {
|
|
pgp_revocation_status_t rs = pgp_tpk_revocation_status(tpk);
|
|
pgp_revocation_status_variant_t rsv = pgp_revocation_status_variant(rs);
|
|
pgp_revocation_status_free(rs);
|
|
if (rsv == PGP_REVOCATION_STATUS_REVOKED)
|
|
revoked = true;
|
|
}
|
|
|
|
if (revoked && ! keylist)
|
|
return keyinfo_list;
|
|
|
|
int dealloc_fpr = 0;
|
|
if (!fpr) {
|
|
dealloc_fpr = 1;
|
|
fpr = pgp_tpk_fingerprint(tpk);
|
|
}
|
|
char *fpr_str = pgp_fingerprint_to_hex(fpr);
|
|
|
|
if (!revoked && keyinfo_list) {
|
|
char *user_id = pgp_tpk_primary_user_id(tpk);
|
|
if (user_id)
|
|
keyinfo_list = stringpair_list_add(keyinfo_list,
|
|
new_stringpair(fpr_str, user_id));
|
|
free(user_id);
|
|
}
|
|
|
|
if (keylist)
|
|
keylist = stringlist_add(keylist, fpr_str);
|
|
|
|
free(fpr_str);
|
|
if (dealloc_fpr)
|
|
pgp_fingerprint_free(fpr);
|
|
|
|
return keyinfo_list;
|
|
}
|
|
|
|
static PEP_STATUS list_keys(PEP_SESSION session,
|
|
const char* pattern, int private_only,
|
|
stringpair_list_t** keyinfo_list, stringlist_t** keylist)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_tpk_t tpk = NULL;
|
|
pgp_fingerprint_t fpr = NULL;
|
|
|
|
T("('%s', private: %d)", pattern, private_only);
|
|
|
|
stringpair_list_t* _keyinfo_list = NULL;
|
|
if (keyinfo_list) {
|
|
_keyinfo_list = new_stringpair_list(NULL);
|
|
if (!_keyinfo_list)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "new_stringpair_list");
|
|
}
|
|
stringlist_t* _keylist = NULL;
|
|
if (keylist) {
|
|
_keylist = new_stringlist(NULL);
|
|
if (!_keylist)
|
|
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "new_string_list");
|
|
}
|
|
|
|
// Trim any leading space. This also makes it easier to recognize
|
|
// a string that is only whitespace.
|
|
while (*pattern == ' ')
|
|
pattern ++;
|
|
|
|
if (strchr(pattern, '@')) {
|
|
// Looks like a mailbox.
|
|
pgp_tpk_t *tpks = NULL;
|
|
int count = 0;
|
|
status = tpk_find_by_email(session, pattern, private_only, &tpks, &count);
|
|
ERROR_OUT(NULL, status, "Looking up '%s'", pattern);
|
|
for (int i = 0; i < count; i ++) {
|
|
add_key(session, _keyinfo_list, _keylist, tpks[i], NULL);
|
|
pgp_tpk_free(tpks[i]);
|
|
}
|
|
free(tpks);
|
|
|
|
if (count == 0) {
|
|
// If match failed, check to see if we've got a dotted
|
|
// address in the pattern. If so, try again without dots.
|
|
const char* dotpos = strstr(pattern, ".");
|
|
const char* atpos = strstr(pattern, "@");
|
|
if (dotpos && atpos && (dotpos < atpos)) {
|
|
char* undotted = _undot_address(pattern);
|
|
if (undotted) {
|
|
PEP_STATUS status = list_keys(session, undotted, private_only,
|
|
keyinfo_list, keylist);
|
|
free(undotted);
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
} else if (// Only hex characters and spaces
|
|
pattern[strspn(pattern, "0123456789aAbBcCdDeEfF ")] == 0
|
|
// And a fair amount of them.
|
|
&& strlen(pattern) >= 16) {
|
|
// Fingerprint. Note: the pep engine never looks keys up by
|
|
// keyid, so we don't handle them.
|
|
fpr = pgp_fingerprint_from_hex(pattern);
|
|
status = tpk_find_by_fpr(session, fpr, false, &tpk, NULL);
|
|
ERROR_OUT(NULL, status, "Looking up key");
|
|
add_key(session, _keyinfo_list, _keylist, tpk, fpr);
|
|
} else if (pattern[0] == 0) {
|
|
// Empty string.
|
|
|
|
pgp_tpk_t *tpks = NULL;
|
|
int count = 0;
|
|
status = tpk_all(session, private_only, &tpks, &count);
|
|
ERROR_OUT(NULL, status, "Looking up '%s'", pattern);
|
|
for (int i = 0; i < count; i ++) {
|
|
add_key(session, _keyinfo_list, _keylist, tpks[i], NULL);
|
|
pgp_tpk_free(tpks[i]);
|
|
}
|
|
free(tpks);
|
|
} else {
|
|
T("unsupported pattern '%s'", pattern);
|
|
}
|
|
|
|
out:
|
|
if (tpk)
|
|
pgp_tpk_free(tpk);
|
|
if (fpr)
|
|
pgp_fingerprint_free(fpr);
|
|
|
|
if (status == PEP_KEY_NOT_FOUND)
|
|
status = PEP_STATUS_OK;
|
|
|
|
if (status != PEP_STATUS_OK || (_keyinfo_list && !_keyinfo_list->value)) {
|
|
free_stringpair_list(_keyinfo_list);
|
|
_keyinfo_list = NULL;
|
|
}
|
|
if (keyinfo_list)
|
|
*keyinfo_list = _keyinfo_list;
|
|
|
|
if (status != PEP_STATUS_OK || (_keylist && !_keylist->value)) {
|
|
free_stringlist(_keylist);
|
|
_keylist = NULL;
|
|
}
|
|
if (keylist)
|
|
*keylist = _keylist;
|
|
|
|
int len = -1;
|
|
if (keylist)
|
|
len = stringlist_length(*keylist);
|
|
else if (keyinfo_list)
|
|
len = stringpair_list_length(*keyinfo_list);
|
|
T("(%s) -> %s (%d keys)", pattern, pEp_status_to_string(status), len);
|
|
return status;
|
|
}
|
|
|
|
// pattern could be empty, an fpr, or a mailbox.
|
|
//
|
|
// keyinfo_list is a list of <fpr, openpgp userid> tuples for the
|
|
// matching keys.
|
|
//
|
|
// This function filters out revoked key, but not expired keys.
|
|
PEP_STATUS pgp_list_keyinfo(PEP_SESSION session,
|
|
const char* pattern,
|
|
stringpair_list_t** keyinfo_list)
|
|
{
|
|
return list_keys(session, pattern, false, keyinfo_list, NULL);
|
|
}
|
|
|
|
PEP_STATUS pgp_recv_key(PEP_SESSION session, const char *pattern)
|
|
{
|
|
assert(!"pgp_recv_key not implemented");
|
|
return PEP_UNKNOWN_ERROR;
|
|
}
|
|
|
|
// Unlike pgp_list_keyinfo, this function returns revoked keys.
|
|
PEP_STATUS pgp_find_keys(
|
|
PEP_SESSION session, const char *pattern, stringlist_t **keylist)
|
|
{
|
|
return list_keys(session, pattern, false, NULL, keylist);
|
|
}
|
|
|
|
// Unlike pgp_list_keyinfo, this function returns revoked keys.
|
|
PEP_STATUS pgp_find_private_keys(
|
|
PEP_SESSION session, const char *pattern, stringlist_t **keylist)
|
|
{
|
|
return list_keys(session, pattern, true, NULL, keylist);
|
|
}
|
|
|
|
PEP_STATUS pgp_send_key(PEP_SESSION session, const char *pattern)
|
|
{
|
|
assert(!"pgp_send_key not implemented");
|
|
return PEP_UNKNOWN_ERROR;
|
|
}
|
|
|
|
PEP_STATUS pgp_get_key_rating(
|
|
PEP_SESSION session, const char *fpr, PEP_comm_type *comm_type)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_tpk_t tpk = NULL;
|
|
|
|
assert(session);
|
|
assert(fpr);
|
|
assert(comm_type);
|
|
|
|
*comm_type = PEP_ct_unknown;
|
|
|
|
pgp_fingerprint_t pgp_fpr = pgp_fingerprint_from_hex(fpr);
|
|
status = tpk_find_by_fpr(session, pgp_fpr, false, &tpk, NULL);
|
|
pgp_fingerprint_free(pgp_fpr);
|
|
ERROR_OUT(NULL, status, "Looking up key: %s", fpr);
|
|
|
|
*comm_type = PEP_ct_OpenPGP_unconfirmed;
|
|
|
|
if (pgp_tpk_expired(tpk)) {
|
|
*comm_type = PEP_ct_key_expired;
|
|
goto out;
|
|
}
|
|
|
|
pgp_revocation_status_t rs = pgp_tpk_revocation_status(tpk);
|
|
pgp_revocation_status_variant_t rsv = pgp_revocation_status_variant(rs);
|
|
pgp_revocation_status_free(rs);
|
|
if (rsv == PGP_REVOCATION_STATUS_REVOKED) {
|
|
*comm_type = PEP_ct_key_revoked;
|
|
goto out;
|
|
}
|
|
|
|
PEP_comm_type best_enc = PEP_ct_no_encryption, best_sign = PEP_ct_no_encryption;
|
|
pgp_tpk_key_iter_t key_iter = pgp_tpk_key_iter_valid(tpk);
|
|
pgp_key_t key;
|
|
pgp_signature_t sig;
|
|
pgp_revocation_status_t rev;
|
|
while ((key = pgp_tpk_key_iter_next(key_iter, &sig, &rev))) {
|
|
if (! sig)
|
|
continue;
|
|
|
|
PEP_comm_type curr = PEP_ct_no_encryption;
|
|
|
|
int can_enc = pgp_signature_can_encrypt_for_transport(sig)
|
|
|| pgp_signature_can_encrypt_at_rest(sig);
|
|
int can_sign = pgp_signature_can_sign(sig);
|
|
|
|
pgp_public_key_algo_t pk_algo = pgp_key_public_key_algo(key);
|
|
if (pk_algo == PGP_PUBLIC_KEY_ALGO_RSA_ENCRYPT_SIGN
|
|
|| pk_algo == PGP_PUBLIC_KEY_ALGO_RSA_ENCRYPT
|
|
|| pk_algo == PGP_PUBLIC_KEY_ALGO_RSA_SIGN) {
|
|
int bits = pgp_key_public_key_bits(key);
|
|
if (bits < 1024)
|
|
curr = PEP_ct_key_too_short;
|
|
else if (bits == 1024)
|
|
curr = PEP_ct_OpenPGP_weak_unconfirmed;
|
|
else
|
|
curr = PEP_ct_OpenPGP_unconfirmed;
|
|
} else {
|
|
curr = PEP_ct_OpenPGP_unconfirmed;
|
|
}
|
|
|
|
if (can_enc)
|
|
best_enc = _MAX(best_enc, curr);
|
|
|
|
if (can_sign)
|
|
best_sign = _MAX(best_sign, curr);
|
|
}
|
|
pgp_tpk_key_iter_free(key_iter);
|
|
|
|
if (best_enc == PEP_ct_no_encryption || best_sign == PEP_ct_no_encryption) {
|
|
*comm_type = PEP_ct_key_b0rken;
|
|
goto out;
|
|
} else {
|
|
*comm_type = _MIN(best_enc, best_sign);
|
|
}
|
|
|
|
out:
|
|
if (tpk)
|
|
pgp_tpk_free(tpk);
|
|
|
|
T("(%s) -> %s", fpr, pep_comm_type_to_string(*comm_type));
|
|
return status;
|
|
}
|
|
|
|
|
|
PEP_STATUS pgp_renew_key(
|
|
PEP_SESSION session, const char *fpr, const timestamp *ts)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_error_t err = NULL;
|
|
pgp_tpk_t tpk = NULL;
|
|
pgp_tpk_key_iter_t iter = NULL;
|
|
pgp_key_pair_t keypair = NULL;
|
|
pgp_signer_t signer = NULL;
|
|
time_t t = mktime((struct tm *) ts);
|
|
|
|
T("(%s)", fpr);
|
|
|
|
status = tpk_find_by_fpr_hex(session, fpr, true, &tpk, NULL);
|
|
ERROR_OUT(NULL, status, "Looking up '%s'", fpr);
|
|
|
|
uint32_t creation_time = pgp_key_creation_time(pgp_tpk_primary(tpk));
|
|
if (creation_time > t)
|
|
// The creation time is after the expiration time!
|
|
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR,
|
|
"creation time can't be after expiration time");
|
|
|
|
uint32_t delta = t - creation_time;
|
|
|
|
|
|
iter = pgp_tpk_key_iter_valid(tpk);
|
|
pgp_tpk_key_iter_certification_capable (iter);
|
|
pgp_tpk_key_iter_unencrypted_secret (iter, true);
|
|
|
|
// If there are multiple certification capable subkeys, we just
|
|
// take the first one, whichever one that happens to be.
|
|
pgp_key_t key = pgp_tpk_key_iter_next (iter, NULL, NULL);
|
|
if (! key)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR,
|
|
"%s has no usable certification capable key", fpr);
|
|
|
|
keypair = pgp_key_into_key_pair (NULL, pgp_key_clone (key));
|
|
if (! keypair)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "Creating a keypair");
|
|
|
|
signer = pgp_key_pair_as_signer (keypair);
|
|
if (! signer)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "Creating a signer");
|
|
|
|
tpk = pgp_tpk_set_expiry(&err, tpk, signer, delta);
|
|
if (! tpk)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "setting expiration");
|
|
|
|
status = tpk_save(session, tpk, NULL);
|
|
tpk = NULL;
|
|
ERROR_OUT(NULL, status, "Saving %s", fpr);
|
|
|
|
out:
|
|
if (signer)
|
|
pgp_signer_free (signer);
|
|
if (keypair)
|
|
pgp_key_pair_free (keypair);
|
|
if (iter)
|
|
pgp_tpk_key_iter_free (iter);
|
|
if (tpk)
|
|
pgp_tpk_free(tpk);
|
|
|
|
T("(%s) -> %s", fpr, pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
PEP_STATUS pgp_revoke_key(
|
|
PEP_SESSION session, const char *fpr, const char *reason)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_error_t err = NULL;
|
|
pgp_tpk_t tpk = NULL;
|
|
pgp_tpk_key_iter_t iter = NULL;
|
|
pgp_key_pair_t keypair = NULL;
|
|
pgp_signer_t signer = NULL;
|
|
|
|
T("(%s)", fpr);
|
|
|
|
status = tpk_find_by_fpr_hex(session, fpr, true, &tpk, NULL);
|
|
ERROR_OUT(NULL, status, "Looking up %s", fpr);
|
|
|
|
iter = pgp_tpk_key_iter_valid(tpk);
|
|
pgp_tpk_key_iter_certification_capable (iter);
|
|
pgp_tpk_key_iter_unencrypted_secret (iter, true);
|
|
|
|
// If there are multiple certification capable subkeys, we just
|
|
// take the first one, whichever one that happens to be.
|
|
pgp_key_t key = pgp_tpk_key_iter_next (iter, NULL, NULL);
|
|
if (! key)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR,
|
|
"%s has no usable certification capable key", fpr);
|
|
|
|
keypair = pgp_key_into_key_pair (NULL, pgp_key_clone (key));
|
|
if (! keypair)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "Creating a keypair");
|
|
|
|
signer = pgp_key_pair_as_signer (keypair);
|
|
if (! signer)
|
|
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "Creating a signer");
|
|
|
|
tpk = pgp_tpk_revoke_in_place(&err, tpk, signer,
|
|
PGP_REASON_FOR_REVOCATION_UNSPECIFIED,
|
|
reason);
|
|
if (! tpk)
|
|
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "setting expiration");
|
|
|
|
assert(pgp_revocation_status_variant(pgp_tpk_revocation_status(tpk))
|
|
== PGP_REVOCATION_STATUS_REVOKED);
|
|
|
|
status = tpk_save(session, tpk, NULL);
|
|
tpk = NULL;
|
|
ERROR_OUT(NULL, status, "Saving %s", fpr);
|
|
|
|
out:
|
|
if (signer)
|
|
pgp_signer_free (signer);
|
|
if (keypair)
|
|
pgp_key_pair_free (keypair);
|
|
if (iter)
|
|
pgp_tpk_key_iter_free (iter);
|
|
if (tpk)
|
|
pgp_tpk_free(tpk);
|
|
|
|
T("(%s) -> %s", fpr, pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
PEP_STATUS pgp_key_expired(PEP_SESSION session, const char *fpr,
|
|
const time_t when, bool *expired)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_tpk_t tpk = NULL;
|
|
T("(%s)", fpr);
|
|
|
|
assert(session);
|
|
assert(fpr);
|
|
assert(expired);
|
|
|
|
*expired = false;
|
|
|
|
pgp_fingerprint_t pgp_fpr = pgp_fingerprint_from_hex(fpr);
|
|
status = tpk_find_by_fpr(session, pgp_fpr, false, &tpk, NULL);
|
|
pgp_fingerprint_free(pgp_fpr);
|
|
ERROR_OUT(NULL, status, "Looking up %s", fpr);
|
|
|
|
// Is the TPK live?
|
|
*expired = !pgp_tpk_alive_at(tpk, when);
|
|
if (*expired)
|
|
goto out;
|
|
|
|
// Are there at least one certification subkey, one signing subkey
|
|
// and one encryption subkey that are live?
|
|
int can_certify = 0, can_encrypt = 0, can_sign = 0;
|
|
|
|
pgp_tpk_key_iter_t key_iter = pgp_tpk_key_iter_valid(tpk);
|
|
pgp_key_t key;
|
|
pgp_signature_t sig;
|
|
pgp_revocation_status_t rev;
|
|
while ((key = pgp_tpk_key_iter_next(key_iter, &sig, &rev))) {
|
|
if (! sig)
|
|
continue;
|
|
|
|
if (pgp_signature_can_encrypt_for_transport(sig)
|
|
|| pgp_signature_can_encrypt_at_rest(sig))
|
|
can_encrypt = 1;
|
|
if (pgp_signature_can_sign(sig))
|
|
can_sign = 1;
|
|
if (pgp_signature_can_certify(sig))
|
|
can_certify = 1;
|
|
|
|
if (can_encrypt && can_sign && can_certify)
|
|
break;
|
|
}
|
|
pgp_tpk_key_iter_free(key_iter);
|
|
|
|
*expired = !(can_encrypt && can_sign && can_certify);
|
|
|
|
out:
|
|
if (tpk)
|
|
pgp_tpk_free(tpk);
|
|
T("(%s) -> %s", fpr, pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
PEP_STATUS pgp_key_revoked(PEP_SESSION session, const char *fpr, bool *revoked)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_tpk_t tpk;
|
|
|
|
T("(%s)", fpr);
|
|
|
|
assert(session);
|
|
assert(fpr);
|
|
assert(revoked);
|
|
|
|
*revoked = false;
|
|
|
|
pgp_fingerprint_t pgp_fpr = pgp_fingerprint_from_hex(fpr);
|
|
status = tpk_find_by_fpr(session, pgp_fpr, false, &tpk, NULL);
|
|
pgp_fingerprint_free(pgp_fpr);
|
|
ERROR_OUT(NULL, status, "Looking up %s", fpr);
|
|
|
|
pgp_revocation_status_t rs = pgp_tpk_revocation_status(tpk);
|
|
*revoked = pgp_revocation_status_variant(rs) == PGP_REVOCATION_STATUS_REVOKED;
|
|
pgp_revocation_status_free (rs);
|
|
pgp_tpk_free(tpk);
|
|
|
|
out:
|
|
T("(%s) -> %s", fpr, pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
PEP_STATUS pgp_key_created(PEP_SESSION session, const char *fpr, time_t *created)
|
|
{
|
|
PEP_STATUS status = PEP_STATUS_OK;
|
|
pgp_tpk_t tpk = NULL;
|
|
T("(%s)", fpr);
|
|
|
|
*created = 0;
|
|
|
|
pgp_fingerprint_t pgp_fpr = pgp_fingerprint_from_hex(fpr);
|
|
status = tpk_find_by_fpr(session, pgp_fpr, false, &tpk, NULL);
|
|
pgp_fingerprint_free(pgp_fpr);
|
|
ERROR_OUT(NULL, status, "Looking up %s", fpr);
|
|
|
|
pgp_key_t k = pgp_tpk_primary(tpk);
|
|
*created = pgp_key_creation_time(k);
|
|
pgp_tpk_free(tpk);
|
|
|
|
out:
|
|
T("(%s) -> %s", fpr, pEp_status_to_string(status));
|
|
return status;
|
|
}
|
|
|
|
PEP_STATUS pgp_binary(const char **path)
|
|
{
|
|
*path = NULL;
|
|
return PEP_STATUS_OK;
|
|
}
|
|
|
|
PEP_STATUS pgp_contains_priv_key(PEP_SESSION session, const char *fpr,
|
|
bool *has_private)
|
|
{
|
|
T("(%s)", fpr);
|
|
pgp_fingerprint_t pgp_fpr = pgp_fingerprint_from_hex(fpr);
|
|
PEP_STATUS status = tpk_find_by_fpr(session, pgp_fpr, true, NULL, NULL);
|
|
pgp_fingerprint_free(pgp_fpr);
|
|
if (status == PEP_STATUS_OK) {
|
|
*has_private = 1;
|
|
} else if (status == PEP_KEY_NOT_FOUND) {
|
|
*has_private = 0;
|
|
status = PEP_STATUS_OK;
|
|
}
|
|
T("(%s) -> %s, %s",
|
|
fpr, *has_private ? "priv" : " |