You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

4277 lines
144 KiB

/**
* @file src/pgp_sequoia.c
*
* @brief Sequoia PGP driver - implements required cryptotech
* functions for the engine using sequoia-pgp
*
* @license GNU General Public License 3.0 - see LICENSE.txt
*
* @see https://sequoia-pgp.org/
* @see https://docs.sequoia-pgp.org/sequoia_ffi/index.html
*/
#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 <stdlib.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"
# ifdef ANDROID
# include <android/log.h>
# define _T(...) do { \
__android_log_print(ANDROID_LOG_DEBUG, "pEpEngine-sequoia", \
##__VA_ARGS__); \
} while (0)
# elif _WIN32
# define _T(...) do { \
char str[256]; \
snprintf(str, 256, ##__VA_ARGS__); \
OutputDebugStringA(str); \
fprintf(stderr, ##__VA_ARGS__); \
} while (0)
# else
# define _T(...) do { \
fprintf(stderr, ##__VA_ARGS__); \
} while (0)
# endif
#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 dump 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)
#ifdef _PEP_SQLITE_DEBUG
/**
* @internal
*
* <!-- sq_sql_trace_callback() -->
*
* @brief TODO
*
* @param[in] trace_constant unsigned
* @param[in] *context_ptr void
* @param[in] *P void
* @param[in] *X void
*
*/
int sq_sql_trace_callback (unsigned trace_constant,
void* context_ptr,
void* P,
void* X) {
switch (trace_constant) {
case SQLITE_TRACE_STMT:
fprintf(stderr, "SEQUOIA_SQL_DEBUG: STMT - ");
const char* X_str = (const char*) X;
if (!EMPTYSTR(X_str) && X_str[0] == '-' && X_str[1] == '-')
fprintf(stderr, "%s\n", X_str);
else
fprintf(stderr, "%s\n", sqlite3_expanded_sql((sqlite3_stmt*)P));
break;
case SQLITE_TRACE_ROW:
fprintf(stderr, "SEQUOIA_SQL_DEBUG: ROW - ");
fprintf(stderr, "%s\n", sqlite3_expanded_sql((sqlite3_stmt*)P));
break;
case SQLITE_TRACE_CLOSE:
fprintf(stderr, "SEQUOIA_SQL_DEBUG: CLOSE - ");
break;
default:
break;
}
return 0;
}
#endif
/*
* This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX
* if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW
*/
#define PEP_MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
/**
* @internal
*
* <!-- _pEp_reallocarray() -->
*
* @brief This is reallocarray taken from OpenBSD. See README.md for licensing.
*
* @param[in,out] optr pointer to memory block whose
* size must change. If optr is NULL,
* a new block is allocated
* @param[in] nmemb number of total members there should be room for
* in the updated array
* @param[in] size Size of an array member
*
* @note Symbols are renamed for clashes, not to hide source.
*
* @see README.md
* @see https://man7.org/linux/man-pages/man3/reallocarray.3.html
*/
static void* _pEp_reallocarray(void *optr, size_t nmemb, size_t size)
{
if ((nmemb >= PEP_MUL_NO_OVERFLOW || size >= PEP_MUL_NO_OVERFLOW) &&
nmemb > 0 && SIZE_MAX / nmemb < size) {
errno = ENOMEM;
return NULL;
}
return realloc(optr, size * nmemb);
}
PEP_STATUS pgp_config_cipher_suite(PEP_SESSION session,
PEP_CIPHER_SUITE suite)
{
switch (suite) {
// supported cipher suites
case PEP_CIPHER_SUITE_RSA2K:
case PEP_CIPHER_SUITE_RSA3K:
case PEP_CIPHER_SUITE_CV25519:
case PEP_CIPHER_SUITE_P256:
case PEP_CIPHER_SUITE_P384:
case PEP_CIPHER_SUITE_P521:
session->cipher_suite = suite;
return PEP_STATUS_OK;
case PEP_CIPHER_SUITE_DEFAULT:
session->cipher_suite = PEP_CIPHER_SUITE_RSA2K;
return PEP_STATUS_OK;
// unsupported cipher suites
default:
session->cipher_suite = PEP_CIPHER_SUITE_RSA2K;
return PEP_CANNOT_CONFIG;
}
}
/**
* @internal
*
* <!-- cipher_suite() -->
*
* @brief Given the pEp cipher suite indicator enum, return the
* equivalent sequoia cipher suite enum value
*
* @param[in] suite pEp-internal cipher suite enum value
*
* @retval sequoia-internal cipher suite enum value
*
* @see pgp_cert_cipher_suite_t
*/
static pgp_cert_cipher_suite_t cipher_suite(PEP_CIPHER_SUITE suite)
{
switch (suite) {
// supported cipher suites
case PEP_CIPHER_SUITE_RSA2K:
return PGP_CERT_CIPHER_SUITE_RSA2K;
case PEP_CIPHER_SUITE_RSA3K:
return PGP_CERT_CIPHER_SUITE_RSA3K;
case PEP_CIPHER_SUITE_CV25519:
return PGP_CERT_CIPHER_SUITE_CV25519;
case PEP_CIPHER_SUITE_P256:
return PGP_CERT_CIPHER_SUITE_P256;
case PEP_CIPHER_SUITE_P384:
return PGP_CERT_CIPHER_SUITE_P384;
case PEP_CIPHER_SUITE_P521:
return PGP_CERT_CIPHER_SUITE_P521;
default:
return PGP_CERT_CIPHER_SUITE_RSA2K;
}
}
/**
* @internal
*
* <!-- email_cmp() -->
*
* @brief Compare the input strings as normalised addresses, somehow,
* and return an integer that is negative, zero, or positive if the
* first string is less than, equal to, or greater than the
* second, respectively.
*
* @param[in] *cookie void
* @param[in] a_len int
* @param[in] *a const void
* @param[in] b_len int
* @param[in] *b const void
*
* @retval 0 if a == b
* @retval >0 if a > b
* @retval <0 if a < b
*
* @todo fix brief, figure out what kind of normalisation is going
* on here and the use case
*/
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);
char *a_email = NULL;
pgp_user_id_email_normalized(NULL, a_userid, &a_email);
if (!a_email)
pgp_user_id_uri(NULL, a_userid, &a_email);
char *b_email = NULL;
pgp_user_id_email_normalized(NULL, b_userid, &b_email);
if (!b_email)
pgp_user_id_uri(NULL, b_userid, &b_email);
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_email && !b_email)
result = 0;
else if (!a_email)
result = -1;
else if (!b_email)
result = 1;
else
result = strcmp(a_email, b_email);
if (true) {
T("'%s' %s '%s'",
a_email,
result == 0 ? "==" : result < 0 ? "<" : ">",
b_email);
}
free(a_email);
free(b_email);
return result;
}
/**
* @internal
*
* <!-- _pgp_get_decrypted_key() -->
*
* @brief TODO
*
* @param[in] session session handle
* @param[in] iter pgp_cert_valid_key_iter_t
* @param[in] *decrypted_key pgp_key_t
*
*/
// Decrypts the key.
//
// This function takes ownership of key (key must be owned; not a
// reference).
//
// On success, it sets *decrypt_key to the decrypted key, which the
// caller owns, and returns PEP_STATUS_OK. On failure, key is freed,
// *decrypted_key is set to NULL, and an error is returned.
static PEP_STATUS _pgp_get_decrypted_key(PEP_SESSION session,
pgp_key_t key,
pgp_key_t* decrypted_key) {
PEP_STATUS status = PEP_STATUS_OK;
pgp_error_t err = NULL;
pgp_fingerprint_t pgp_fpr = pgp_key_fingerprint(key);
char *fpr = pgp_fingerprint_to_hex(pgp_fpr);
T("(%s)", fpr);
if (!decrypted_key)
ERROR_OUT (err, PEP_ILLEGAL_VALUE, "missing decrypted_key parameter");
*decrypted_key = NULL;
if (!key)
ERROR_OUT (err, PEP_ILLEGAL_VALUE, "missing key parameter");
if (pgp_key_has_unencrypted_secret(key)) {
// In case key is a reference (and not an owned value), we
// clone it.
*decrypted_key = key;
key = NULL;
} else {
const char* pass = session->curr_passphrase;
if (pass && pass[0]) {
*decrypted_key = pgp_key_decrypt_secret(&err, key,
(uint8_t*)pass,
strlen(pass));
key = NULL;
if (!*decrypted_key) {
ERROR_OUT (err, PEP_WRONG_PASSPHRASE, "wrong passphrase");
}
} else {
ERROR_OUT (err, PEP_PASSPHRASE_REQUIRED, "passphrase required");
}
}
out:
T("(%s) -> %s", fpr, pEp_status_to_string(status));
pgp_key_free (key);
pgp_fingerprint_free (pgp_fpr);
free (fpr);
return status;
}
// Returns the first key in iter that is already decrypted or can be
// decrypted using the stored passphrase.
//
// This function does not take ownership of iter (the caller must
// still free it).
//
// On success, it sets *decrypt_key to the decrypted key and returns
// PEP_STATUS_OK. On failure, key is freed, *decrypted_key is set to
// NULL, and an error is returned.
static PEP_STATUS _pgp_get_decrypted_key_iter(PEP_SESSION session,
pgp_cert_valid_key_iter_t iter,
pgp_key_t* decrypted_key) {
PEP_STATUS status = PEP_STATUS_OK;
pgp_error_t err = NULL;
if (!decrypted_key)
ERROR_OUT (err, PEP_ILLEGAL_VALUE, "missing decrypt_key parameter");
*decrypted_key = NULL;
if (!iter)
ERROR_OUT (err, PEP_ILLEGAL_VALUE, "missing iter parameter");
bool bad_pass = false;
bool missing_pass = false;
pgp_key_t key = NULL;
pgp_valid_key_amalgamation_t ka
= pgp_cert_valid_key_iter_next (iter, NULL, NULL);
// FIXME: better error!!!
if (! ka)
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "no matching key");
for ( ; ka ; (ka = pgp_cert_valid_key_iter_next(iter, NULL, NULL))) {
// _pgp_get_decrypted_key takes an owned key, but here we only
// get a reference (which we still need to free).
pgp_key_t keyref = pgp_valid_key_amalgamation_key (ka);
key = pgp_key_clone (keyref);
pgp_key_free (keyref);
pgp_valid_key_amalgamation_free (ka);
status = _pgp_get_decrypted_key(session, key, decrypted_key);
if (status == PEP_STATUS_OK)
break;
else if (status == PEP_WRONG_PASSPHRASE)
bad_pass = true;
else if (status == PEP_PASSPHRASE_REQUIRED)
missing_pass = true;
}
if (!*decrypted_key) {
if (bad_pass)
ERROR_OUT(err, PEP_WRONG_PASSPHRASE, "pgp_key_decrypt_secret");
else if (missing_pass)
ERROR_OUT(err, PEP_PASSPHRASE_REQUIRED, "pgp_key_decrypt_secret");
else
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "pgp_valid_key_amalgamation_key");
}
out:
T(" -> %s", pEp_status_to_string(status));
return status;
}
PEP_STATUS pgp_init(PEP_SESSION session, bool in_first)
{
PEP_STATUS status = PEP_STATUS_OK;
#ifdef _WIN32
int sqlite_result;
sqlite_result = sqlite3_open_v2(KEYS_DB,
&session->key_db,
SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
| SQLITE_OPEN_FULLMUTEX
| SQLITE_OPEN_PRIVATECACHE,
NULL);
#else
// Compute a string containing the DB absolute path name.
#define PEP_KEYS_RELATIVE_FILENAME "keys.db"
const char *directory = per_user_directory();
// Create the DB and initialize it.
size_t path_size
= (strlen(directory)
+ 1 /* '/' */
+ strlen(PEP_KEYS_RELATIVE_FILENAME)
+ 1 /* '\0' */);
char *path = (char *) calloc(path_size, 1);
assert(path);
if (!path)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
int r = snprintf(path, path_size, "%s/%s",
directory, PEP_KEYS_RELATIVE_FILENAME);
assert(r >= 0 && r < path_size);
if (r < 0) {
free(path);
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);
#endif
#ifdef _PEP_SQLITE_DEBUG
sqlite3_trace_v2(session->key_db,
SQLITE_TRACE_STMT | SQLITE_TRACE_ROW | SQLITE_TRACE_CLOSE,
sq_sql_trace_callback,
NULL);
#endif
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.cert_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.cert_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.cert_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.cert_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.cert_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.cert_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.cert_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.cert_save_insert_userids, NULL);
assert(sqlite_result == SQLITE_OK);
sqlite_result
= sqlite3_prepare_v2(session->key_db,
"DELETE FROM keys WHERE primary_key = ?",
-1, &session->sq_sql.delete_keypair, NULL);
assert(sqlite_result == SQLITE_OK);
session->policy = pgp_null_policy ();
if (! session->policy)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY,
"initializing openpgp policy");
out:
if (status != PEP_STATUS_OK)
pgp_release(session, in_first);
return status;
}
void pgp_release(PEP_SESSION session, bool out_last)
{
pgp_policy_free (session->policy);
session->policy = NULL;
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;
}
}
/**
* @internal
*
* <!-- pgp_fingerprint_canonicalize() -->
*
* @brief Ensures that a fingerprint is in canonical form. A canonical
* fingerprint doesn't contain any white space.
*
* @param[in] fpr fingerprint to strip whitespace from
*
* @ownership fpr remains with the caller
*/
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 certificate and secret.
/**
* @internal
*
* <!-- key_load() -->
*
* @brief TODO
*
* @param[in] session session handle
* @param[in] stmt
* @param[in] certp
* @param[in] secretp
*
*/
static PEP_STATUS key_load(PEP_SESSION, sqlite3_stmt *, pgp_cert_t *, int *)
__attribute__((nonnull(1, 2)));
static PEP_STATUS key_load(PEP_SESSION session, sqlite3_stmt *stmt,
pgp_cert_t *certp, int *secretp)
{
PEP_STATUS status = PEP_STATUS_OK;
int sqlite_result = sqlite3_step(stmt);
switch (sqlite_result) {
case SQLITE_ROW:
if (certp) {
int data_len = sqlite3_column_bytes(stmt, 0);
const void *data = sqlite3_column_blob(stmt, 0);
pgp_error_t err = NULL;
*certp = pgp_cert_from_bytes(&err, data, data_len);
if (!*certp)
ERROR_OUT(err, PEP_GET_KEY_FAILED, "parsing certificate");
}
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 certificates.
/**
* @internal
*
* <!-- key_loadn() -->
*
* @brief TODO
*
* @param[in] PEP_SESSION session handle
* @param[in] *sqlite3_stmt
* @param[in] **pgp_cert_t
* @param[in] *int
*
*/
static PEP_STATUS key_loadn(PEP_SESSION, sqlite3_stmt *, pgp_cert_t **, int *)
__attribute__((nonnull));
static PEP_STATUS key_loadn(PEP_SESSION session, sqlite3_stmt *stmt,
pgp_cert_t **certsp, int *certs_countp)
{
PEP_STATUS status = PEP_STATUS_OK;
int certs_count = 0;
int certs_capacity = 8;
pgp_cert_t *certs = calloc(certs_capacity, sizeof(pgp_cert_t));
if (!certs)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
for (;;) {
pgp_cert_t cert = NULL;
status = key_load(session, stmt, &cert, NULL);
if (status == PEP_KEY_NOT_FOUND) {
status = PEP_STATUS_OK;
break;
}
ERROR_OUT(NULL, status, "loading certificate");
if (certs_count == certs_capacity) {
certs_capacity *= 2;
certs = realloc(certs, sizeof(certs[0]) * certs_capacity);
if (!certs)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "certs");
}
certs[certs_count ++] = cert;
}
out:
if (status != PEP_STATUS_OK) {
for (int i = 0; i < certs_count; i ++)
pgp_cert_free(certs[i]);
free(certs);
} else {
*certsp = certs;
*certs_countp = certs_count;
}
T(" -> %s (%d certs)", pEp_status_to_string(status), *certs_countp);
return status;
}
/**
* @internal
*
* <!-- cert_find() -->
*
* @brief Returns the certificate identified by the provided fingerprint.
*
* @param[in] session session handle
* @param[in] fpr pgp_fingerprint_t fingerprint
* @param[in] private_only Only return the private key cert?
* (Or only return the cert IF there is one?)
* @param[out] cert desired cert
* @param[out] secret ??? true if it contained a secret key, I guess?
*
* @warning This function only matches on the primary key!
*
* @todo Resolve the above
*/
static PEP_STATUS cert_find(PEP_SESSION, pgp_fingerprint_t, int, pgp_cert_t *, int *)
__attribute__((nonnull(1, 2)));
static PEP_STATUS cert_find(PEP_SESSION session,
pgp_fingerprint_t fpr, int private_only,
pgp_cert_t *cert, 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.cert_find;
sqlite3_bind_text(stmt, 1, fpr_str, -1, SQLITE_STATIC);
status = key_load(session, stmt, cert, 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;
}
/**
* @internal
*
* <!-- cert_find_by_keyid_hex() -->
*
* @brief Returns the certificate identified by the provided keyid.
*
* @param[in] session session handle
* @param[in] keyid_hex the hex key id of the key to retrieve
* (can be primary or subkey)
* @param[in] private_only if true, only consider certificates with
* some secret key material
* @param[out] certp desired cert, if found
* @param[out] secretp ???
*
* @warning This function matches on both primary keys and subkeys!
*
* @note There can be multiple certificates for a given keyid. This can
* occur, because an encryption subkey can be bound to multiple certificates.
* Also, it is possible to collide key ids. If there are multiple key
* ids for a given key, this just returns one of them.
*
*/
static PEP_STATUS cert_find_by_keyid_hex(PEP_SESSION, const char *, int, pgp_cert_t *, int *)
__attribute__((nonnull(1, 2)));
static PEP_STATUS cert_find_by_keyid_hex(
PEP_SESSION session, const char *keyid_hex, int private_only,
pgp_cert_t *certp, 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.cert_find_by_keyid;
sqlite3_bind_text(stmt, 1, keyid_hex, -1, SQLITE_STATIC);
status = key_load(session, stmt, certp, 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;
}
/**
* @internal
*
* <!-- cert_find_by_keyid() -->
*
* @brief TODO
*
* @brief Returns the certificate identified by the provided keyid.
*
* @param[in] session session handle
* @param[in] keyid pgp_keyid_t form of the desired key id
* @param[in] private_only if true, only consider certificates with
* some secret key material
* @param[out] certp desired cert, if found
* @param[out] secretp ???
*
* @see cert_find_by_keyid_hex()
*/
PEP_STATUS cert_find_by_keyid(PEP_SESSION, pgp_keyid_t, int, pgp_cert_t *, int *)
__attribute__((nonnull(1, 2)));
PEP_STATUS cert_find_by_keyid(PEP_SESSION session,
pgp_keyid_t keyid, int private_only,
pgp_cert_t *certp, int *secretp)
{
char *keyid_hex = pgp_keyid_to_hex(keyid);
if (! keyid_hex)
return PEP_OUT_OF_MEMORY;
PEP_STATUS status
= cert_find_by_keyid_hex(session, keyid_hex, private_only, certp, secretp);
free(keyid_hex);
return status;
}
/**
* @internal
*
* <!-- cert_find_by_fpr() -->
*
* @brief Returns the certificate identified by the provided keyid.
*
* @param[in] session session handle
* @param[in] fpr the pgp_fingerprint_t fingerprint
* of the key to retrieve
* (can be primary or subkey)
* @param[in] private_only if true, only consider certificates with
* some secret key material
* @param[out] certp desired cert, if found
* @param[out] secretp ???
*
* @see cert_find_by_keyid_hex()
*/
static PEP_STATUS cert_find_by_fpr(PEP_SESSION, pgp_fingerprint_t, int,
pgp_cert_t *, int *)
__attribute__((nonnull(1, 2)));
static PEP_STATUS cert_find_by_fpr(
PEP_SESSION session, pgp_fingerprint_t fpr, int private_only,
pgp_cert_t *certp, int *secretp)
{
pgp_keyid_t keyid = pgp_fingerprint_to_keyid(fpr);
if (! keyid)
return PEP_OUT_OF_MEMORY;
PEP_STATUS status
= cert_find_by_keyid(session, keyid, private_only, certp, secretp);
pgp_keyid_free(keyid);
return status;
}
/**
* @internal
*
* <!-- cert_find_by_fpr_hex() -->
*
* @brief Returns the certificate identified by the provided keyid.
*
* @param[in] session session handle
* @param[in] pgp_fpr the fingerprint hex (???)
* of the key to retrieve
* (can be primary or subkey)
* @param[in] private_only if true, only consider certificates with
* some secret key material
* @param[out] certp desired cert, if found
* @param[out] secretp ???
*
* @todo resolve the above
*
* @see cert_find_by_keyid_hex()
*/
static PEP_STATUS cert_find_by_fpr_hex(PEP_SESSION, const char *, int, pgp_cert_t *, int *secret)
__attribute__((nonnull(1, 2)));
static PEP_STATUS cert_find_by_fpr_hex(
PEP_SESSION session, const char *fpr, int private_only,
pgp_cert_t *certp, int *secretp)
{
pgp_fingerprint_t pgp_fpr = pgp_fingerprint_from_hex(fpr);
if (! pgp_fpr)
return PEP_OUT_OF_MEMORY;
PEP_STATUS status
= cert_find_by_fpr(session, pgp_fpr, private_only, certp, secretp);
pgp_fingerprint_free(pgp_fpr);
return status;
}
/**
* @internal
*
* <!-- cert_all() -->
*
* @brief Returns all known certificates.
*
* @param[in] session session handle
* @param[in] private_only if true, only return keys which
* contain secret keys (???)
* @param[out] certsp Returns the array of found certs
* @param[out] certs_countsp Returns the count of found certs
*
* @pre certsp is non-NULL
* @pre certs_countsp is non-NULL
*
*/
static PEP_STATUS cert_all(PEP_SESSION, int, pgp_cert_t **, int *) __attribute__((nonnull));
static PEP_STATUS cert_all(PEP_SESSION session, int private_only,
pgp_cert_t **certsp, int *certs_countp) {
PEP_STATUS status = PEP_STATUS_OK;
sqlite3_stmt *stmt = private_only ? session->sq_sql.tsk_all : session->sq_sql.cert_all;
status = key_loadn(session, stmt, certsp, certs_countp);
ERROR_OUT(NULL, status, "loading certificates");
out:
sqlite3_reset(stmt);
return status;
}
/**
* @internal
*
* <!-- cert_find_by_email() -->
*
* @brief Returns keys that have a user id that matches the specified pattern.
*
* @param[in] session session handle
* @param[in] pattern pattern to search for in uids
* @param[in] private_only if true, only return keys which
* contain secret keys (???)
* @param[out] certsp Returns the array of found certs
* @param[out] countp Returns the count of found certs
*
* @pre certsp is non-NULL
* @pre certs_countsp is non-NULL
*
* @warning The keys returned must be freed using pgp_cert_free.
*
*/
static PEP_STATUS cert_find_by_email(PEP_SESSION, const char *, int, pgp_cert_t **, int *)
__attribute__((nonnull));
static PEP_STATUS cert_find_by_email(PEP_SESSION session,
const char *pattern, int private_only,
pgp_cert_t **certsp, 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.cert_find_by_email;
sqlite3_bind_text(stmt, 1, pattern, -1, SQLITE_STATIC);
status = key_loadn(session, stmt, certsp, 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;
}
// end detect possibly changed key stuff ????
/**
* @internal
*
* <!-- serialize_cert() -->
*
* @brief Serialise this certificate (likely for writing to file)
*
* @param[in] session session handle
* @param[in] cert certificate to be serialised
* @param[out] buffer_ptr Serialised certificate data
* @param[out] buffer_size_ptr Size of serialised certificate data
*
* @todo address the above
*/
static PEP_STATUS serialize_cert(PEP_SESSION session, pgp_cert_t cert,
void** buffer_ptr, size_t* buffer_size_ptr) {
if (!session || !cert || !buffer_ptr || !buffer_size_ptr)
return PEP_ILLEGAL_VALUE;
PEP_STATUS status = PEP_STATUS_OK;
void* curr_buffer = NULL;
size_t curr_buffer_len = 0;
pgp_status_t pgp_status;
pgp_tsk_t tsk = NULL;
pgp_error_t err = NULL;
pgp_writer_t writer = pgp_writer_alloc(&curr_buffer, &curr_buffer_len);
if (!writer)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
tsk = pgp_cert_as_tsk(cert);
pgp_status = pgp_tsk_serialize(&err, tsk, writer);
if (pgp_status != 0)
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Serializing certificates");
out:
pgp_tsk_free(tsk);
pgp_writer_free(writer);
if (status == PEP_STATUS_OK) {
*buffer_ptr = curr_buffer;
*buffer_size_ptr = curr_buffer_len;
}
else
free(buffer_ptr);
T(" -> %s", pEp_status_to_string(status));
return status;
}
// Saves the specified certificates.
//
// This function takes ownership of CERT.
static PEP_STATUS cert_save(PEP_SESSION, pgp_cert_t, identity_list **, bool* changed_ptr)
__attribute__((nonnull(1, 2)));
static PEP_STATUS cert_save(PEP_SESSION session, pgp_cert_t cert,
identity_list **private_idents, bool* changed_ptr)
{
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;
void *curr_buffer = NULL;
size_t curr_buffer_len = 0;
int tried_commit = 0;
pgp_cert_key_iter_t key_iter = NULL;
pgp_cert_valid_user_id_iter_t ua_iter = NULL;
pgp_valid_user_id_amalgamation_t ua = NULL;
pgp_packet_t user_id = NULL;
char *email = NULL;
char *name = NULL;
bool _changed = false;
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_cert_fingerprint(cert);
fpr = pgp_fingerprint_to_hex(pgp_fpr);
T("(%s, private_idents: %s)", fpr, private_idents ? "yes" : "no");
// Merge any existing data into certificate.
pgp_cert_t current = NULL;
status = cert_find(session, pgp_fpr, false, &current, NULL);
if (status == PEP_KEY_NOT_FOUND)
status = PEP_STATUS_OK;
else
ERROR_OUT(NULL, status, "Looking up %s", fpr);
if (current) {
if (changed_ptr) {
// Serialize current for comparison (ugh).
status = serialize_cert(session, current, &curr_buffer, &curr_buffer_len);
if (status != PEP_STATUS_OK)
ERROR_OUT(NULL, status, "Could not serialize existing cert for change check");
}
cert = pgp_cert_merge_public_and_secret(&err, cert, current);
if (! cert)
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Merging certificates");
}
else if (changed_ptr)
_changed = true;
int is_tsk = pgp_cert_is_tsk(cert);
// Serialize it.
// NOTE: Just because it's called tsk in tsk_buffer does NOT mean it necessarily
// has secret key material; it is just that is could. is_tsk is the
// part that asks whether or not it contains such.
status = serialize_cert(session, cert, &tsk_buffer, &tsk_buffer_len);
if (status != PEP_STATUS_OK)
ERROR_OUT(NULL, status, "Could not serialize tsk cert for saving");
// Before we do anything else, if we need to know if things MAY have changed,
// we check the key blob (this is not comprehensive and can generate false
// positives)
//
if (changed_ptr) {
if (!current || !curr_buffer || (curr_buffer_len != tsk_buffer_len))
_changed = true;
else if (memcmp(curr_buffer, tsk_buffer, tsk_buffer_len) != 0)
_changed = true;
}
// Insert the TSK into the DB.
stmt = session->sq_sql.cert_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 certificate: %s", sqlite3_errmsg(session->key_db));
// Insert the "subkeys" (the primary key and the subkeys).
stmt = session->sq_sql.cert_save_insert_subkeys;
// This inserts all of the keys in the certificate, i.e.,
// including revoked and expired keys, which is what we want.
key_iter = pgp_cert_key_iter(cert);
pgp_key_amalgamation_t ka;
while ((ka = pgp_cert_key_iter_next(key_iter))) {
pgp_key_t key = pgp_key_amalgamation_key (ka);
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);
pgp_key_free (key);
pgp_key_amalgamation_free (ka);
sqlite_result = sqlite3_step(stmt);
sqlite3_reset(stmt);
free(keyid_hex);
pgp_keyid_free(keyid);
if (sqlite_result != SQLITE_DONE) {
pgp_cert_key_iter_free(key_iter);
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR,
"Updating subkeys: %s", sqlite3_errmsg(session->key_db));
}
}
pgp_cert_key_iter_free(key_iter);
key_iter = NULL;
// Insert the "userids".
stmt = session->sq_sql.cert_save_insert_userids;
ua_iter = pgp_cert_valid_user_id_iter(cert, session->policy, 0);
while ((ua = pgp_cert_valid_user_id_iter_next(ua_iter))) {
user_id = pgp_valid_user_id_amalgamation_user_id(ua);
const uint8_t *user_id_value = pgp_user_id_value(user_id, NULL);
if (!user_id_value || !*user_id_value) {
pgp_packet_free (user_id);
user_id = NULL;
pgp_valid_user_id_amalgamation_free(ua);
ua = NULL;
continue;
}
free(name);
name = NULL;
free(email);
email = NULL;
pgp_user_id_name(NULL, user_id, &name);
// Try to get the normalized address.
pgp_user_id_email_normalized(NULL, user_id, &email);
if (! email)
// Ok, it's not a proper RFC 2822 name-addr. Perhaps it
// is a URI.
pgp_user_id_uri(NULL, user_id, &email);
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) {
ERROR_OUT(NULL, PEP_UNKNOWN_ERROR,
"Updating userids: %s", sqlite3_errmsg(session->key_db));
}
if (private_idents && is_tsk) {
// 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");
if (!*private_idents)
*private_idents = new_identity_list(ident);
else
identity_list_add(*private_idents, ident);
}
}
pgp_packet_free (user_id);
user_id = NULL;
pgp_valid_user_id_amalgamation_free(ua);
ua = 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));
if (changed_ptr)
*changed_ptr = _changed;
free(email);
free(name);
pgp_packet_free(user_id);
pgp_valid_user_id_amalgamation_free(ua);
pgp_cert_valid_user_id_iter_free(ua_iter);
pgp_cert_key_iter_free(key_iter);
if (stmt)
sqlite3_reset(stmt);
free(tsk_buffer);
free(curr_buffer);
pgp_cert_free(cert);
free(fpr);
pgp_fingerprint_free(pgp_fpr);
return status;
}
/**
* @internal
*
* @struct decrypt_cookie
*
* @brief Cookie passed back and forth passed to decrypt callbacks to
* communicate information pre and post decrypt
*
* @todo Clarify
*/
struct decrypt_cookie {
PEP_SESSION session;
int get_secret_keys_called;
stringlist_t *recipient_keylist;
stringlist_t *signer_keylist;
int good_checksums;
int malformed_signature;
int missing_keys;
int unbound_key;
int revoked_key;
int expired_key;
int bad_key;
int bad_checksums;
// Whether we decrypted anything.
int decrypted;
int missing_passphrase;
int bad_passphrase;
// The filename stored in the literal data packet. Note: this is
// *not* protected by the signature and should not be trusted!!!
char *filename;
};
static pgp_status_t
get_public_keys_cb(void *cookie_raw,
pgp_keyid_t *keyids, size_t keyids_len,
pgp_cert_t **certs, size_t *certs_len,
void (**our_free)(void *))
{
struct decrypt_cookie *cookie = cookie_raw;
PEP_SESSION session = cookie->session;
*certs = calloc(keyids_len, sizeof(*certs));
if (!*certs)
return PGP_STATUS_UNKNOWN_ERROR;
*our_free = free;
size_t i;
int j = 0;
for (i = 0; i < keyids_len; i ++) {
pgp_cert_t cert = NULL;
PEP_STATUS status
= cert_find_by_keyid(session, keyids[i], false, &cert, NULL);
if (status == PEP_STATUS_OK)
(*certs)[j ++] = cert;
}
*certs_len = j;
return PGP_STATUS_SUCCESS;
}
/**
* @internal
*
* <!-- decrypt_cb() -->
*
* @brief TODO
*
* @param[in] *cookie_opaque void
* @param[in] *pkesks pgp_pkesk_t
* @param[in] pkesk_count size_t
* @param[in] *skesks pgp_skesk_t
* @param[in] skesk_count size_t
* @param[in] symmetric_algo uint8_t
* @param[in] *decrypt pgp_decryptor_do_decrypt_cb_t
* @param[in] *decrypt_cookie void
* @param[in] *identity_out pgp_fingerprint_t
*
*/
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,
uint8_t symmetric_algo,
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_cert_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 (size_t i = 0; i < pkesk_count; i ++) {
pgp_pkesk_t pkesk = pkesks[i];
pgp_keyid_t keyid = pgp_pkesk_recipient(pkesk);
char *keyid_str = pgp_keyid_to_hex(keyid);
pgp_cert_key_iter_t key_iter = NULL;
pgp_key_amalgamation_t ka = NULL;
pgp_key_t key = NULL;
pgp_session_key_t sk = NULL;
pgp_cert_t cert = 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.
int is_tsk = 0;
if (cert_find_by_keyid(session, keyid, false, &cert, &is_tsk) != PEP_STATUS_OK)
goto eol;
pgp_fingerprint_t fp = pgp_cert_fingerprint(cert);
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_cert_is_tsk(cert));
if (! is_tsk)
goto eol;
key_iter = pgp_cert_key_iter(cert);
while (key = NULL, (ka = pgp_cert_key_iter_next(key_iter))) {
key = pgp_key_amalgamation_key (ka);
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;
pgp_key_free (key);
pgp_key_amalgamation_free (ka);
}
if (key == NULL) {
assert(!"Inconsistent DB: key doesn't contain a subkey with keyid!");
goto eol;
}
if (!pgp_key_has_unencrypted_secret(key)) {
const char* pass = session->curr_passphrase;
if (pass && pass[0]) {
pgp_key_t decrypted_key = NULL;
decrypted_key = pgp_key_decrypt_secret(&err, pgp_key_clone(key), (uint8_t*)session->curr_passphrase,
strlen(session->curr_passphrase));
if (!decrypted_key) {
DUMP_ERR(err, PEP_WRONG_PASSPHRASE, "pgp_key_decrypt_secret");
cookie->bad_passphrase = 1;
goto eol;
}
else {
pgp_key_free(key);
key = decrypted_key;
}
}
else {
DUMP_ERR(err, PEP_PASSPHRASE_REQUIRED, "pgp_key_decrypt_secret");
cookie->missing_passphrase = 1;
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);
if (! decrypt (decrypt_cookie, algo, sk)) {
DUMP_STATUS(PGP_STATUS_UNKNOWN_ERROR, PEP_CANNOT_DECRYPT_UNKNOWN, "decrypt_cb");
goto eol;
}
T("Decrypted PKESK for %s", keyid_str);
*identity_out = pgp_cert_fingerprint(cert);
cookie->decrypted = 1;
eol:
pgp_keyid_free (keyid);
pgp_session_key_free (sk);
free(keyid_str);
pgp_key_free (key);
pgp_key_amalgamation_free (ka);
pgp_cert_key_iter_free(key_iter);
pgp_cert_free(cert);
}
// Consider wildcard recipients.
if (wildcards) for (size_t i = 0; i < pkesk_count && !cookie->decrypted; i ++) {
pgp_pkesk_t pkesk = pkesks[i];
pgp_keyid_t keyid = pgp_pkesk_recipient(pkesk);
char *keyid_str = pgp_keyid_to_hex(keyid);
pgp_cert_key_iter_t key_iter = NULL;
pgp_key_amalgamation_t ka = NULL;
pgp_key_t key = NULL;
pgp_session_key_t sk = NULL;
if (strcmp(keyid_str, "0000000000000000") != 0)
goto eol2;
if (!tsks) {
if (cert_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_cert_t tsk = tsks[j];
key_iter = pgp_cert_key_iter(tsk);
while (key = NULL, (ka = pgp_cert_key_iter_next(key_iter))) {
key = pgp_key_amalgamation_key (ka);
if (!pgp_key_has_unencrypted_secret(key)) {
const char* pass = session->curr_passphrase;
if (pass && pass[0]) {
pgp_key_t decrypted_key = NULL;
decrypted_key = pgp_key_decrypt_secret(&err, pgp_key_clone(key), (uint8_t*)session->curr_passphrase,
strlen(session->curr_passphrase));
if (!decrypted_key) {
DUMP_ERR(err, PEP_WRONG_PASSPHRASE, "pgp_key_decrypt_secret");
cookie->bad_passphrase = 1;
continue;
}
else {
pgp_key_free(key);
key = decrypted_key;
}
}
else {
DUMP_ERR(err, PEP_PASSPHRASE_REQUIRED, "pgp_key_decrypt_secret");
cookie->missing_passphrase = 1;
continue;
}
}
// 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;
pgp_key_free (key);
pgp_key_amalgamation_free (ka);
continue;
}
// Add it to the recipient list.
pgp_fingerprint_t fp = pgp_cert_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);
if (! decrypt (decrypt_cookie, algo, sk)) {
DUMP_STATUS(PGP_STATUS_UNKNOWN_ERROR, PEP_CANNOT_DECRYPT_UNKNOWN, "decrypt_cb");
goto eol2;
}
*identity_out = pgp_cert_fingerprint(tsk);
cookie->decrypted = 1;
break;
}
pgp_key_free (key);
key = NULL;
pgp_key_amalgamation_free (ka);
ka = NULL;
pgp_cert_key_iter_free(key_iter);
key_iter = NULL;
}
eol2:
pgp_keyid_free (keyid);
pgp_session_key_free (sk);
free(keyid_str);
pgp_key_free (key);
pgp_key_amalgamation_free (ka);
pgp_cert_key_iter_free(key_iter);
}
if (tsks) {
for (int i = 0; i < tsks_count; i ++)
pgp_cert_free(tsks[i]);
free(tsks);
}
return cookie->decrypted ? PGP_STATUS_SUCCESS : PGP_STATUS_UNKNOWN_ERROR;
}
/**
* @internal
*
* <!-- check_signatures_cb() -->
*
* @brief TODO
*
* @param[in,out] cookie_opaque cookie to add result information to
* (signer keylist, counts of various verification
* errors (expired, revoked, ...), key errors, etc)
* @param[in] structure pgp_message_structure_t ???
*
*/
static pgp_status_t check_signatures_cb(void *cookie_opaque, pgp_message_structure_t structure)
{
struct decrypt_cookie *cookie = cookie_opaque;
pgp_message_structure_iter_t iter
= pgp_message_structure_into_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 = NULL;
pgp_keyid_t keyid = NULL;
char *keyid_str = NULL;
pgp_error_t error = NULL;
char *error_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_cert_t cert = NULL;
pgp_verification_result_good_checksum (result, &sig,
&cert,
NULL, // key
NULL, // binding
NULL); // revocation
// We need the primary key's fingerprint.
pgp_fingerprint_t primary_fpr
= pgp_cert_fingerprint(cert);
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);
free (primary_fpr_str);
pgp_fingerprint_free (primary_fpr);
pgp_cert_free (cert);
cookie->good_checksums ++;
break;
}
case PGP_VERIFICATION_RESULT_MALFORMED_SIGNATURE:
if (TRACING) {
pgp_verification_result_malformed_signature (result,
&sig,
&error);
error_str = pgp_error_to_string(error);
keyid = pgp_signature_issuer (sig);
keyid_str = pgp_keyid_to_string (keyid);
T("Malformed signature from %s: %s",
keyid_str, error_str);
}
cookie->malformed_signature ++;
break;
case PGP_VERIFICATION_RESULT_MISSING_KEY:
if (TRACING) {
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_UNBOUND_KEY:
// This happens if the key doesn't have a binding
// signature.
if (TRACING) {
pgp_verification_result_unbound_key (result,
&sig,
NULL,
&error);
error_str = pgp_error_to_string(error);
keyid = pgp_signature_issuer (sig);
keyid_str = pgp_keyid_to_string (keyid);
T("key %s has no valid self-signature: %s",
keyid_str ? keyid_str : "(missing issuer)",
error_str);
}
cookie->unbound_key ++;
break;
case PGP_VERIFICATION_RESULT_BAD_KEY: {
// This happens if the certificate is not alive or
// revoked, if the key is not alive or revoked, of
// if the key is not signing capable.
pgp_cert_t cert = NULL;
pgp_key_t key = NULL;
pgp_signature_t selfsig = NULL;
pgp_revocation_status_t rs = NULL;
pgp_verification_result_bad_key (result,
&sig,
&cert, // cert
&key, // key
&selfsig, // binding
&rs, // key revocation
&error);
if (TRACING) {
error_str = pgp_error_to_string(error);
keyid = pgp_signature_issuer (sig);
keyid_str = pgp_keyid_to_string (keyid);
T("key %s is bad: %s",
keyid_str ? keyid_str : "(missing issuer)",
error_str);
}
// Check if the key or certificate is revoked.
if (pgp_revocation_status_variant(rs)
== PGP_REVOCATION_STATUS_REVOKED) {
// Key is revoked.
cookie->revoked_key ++;
} else {
pgp_revocation_status_free (rs);
rs = pgp_cert_revocation_status (cert, cookie->session->policy, 0);
if (pgp_revocation_status_variant(rs)
== PGP_REVOCATION_STATUS_REVOKED) {
// Cert is revoked.
cookie->revoked_key ++;
}
// Check if the key or certificate is expired.
else if (pgp_cert_alive(NULL, cert,
cookie->session->policy, 0)
!= PGP_STATUS_SUCCESS) {
// Certificate is expired.
cookie->expired_key ++;
goto out;
} else if (pgp_signature_key_alive (NULL, selfsig, key, 0)
!= PGP_STATUS_SUCCESS) {
// Key is expired.
cookie->expired_key ++;
goto out;
}
// Wrong key flags or something similar.
else {
cookie->bad_key ++;
}
}
out:
pgp_revocation_status_free (rs);
pgp_signature_free (selfsig);
pgp_key_free (key);
pgp_cert_free (cert);
break;
}
case PGP_VERIFICATION_RESULT_BAD_SIGNATURE:
if (TRACING) {
pgp_verification_result_bad_signature
(result, &sig, NULL, NULL, NULL, NULL, &error);
error_str = pgp_error_to_string(error);
keyid = pgp_signature_issuer (sig);
if (keyid) {
keyid_str = pgp_keyid_to_string (keyid);
T("Bad signature from %s: %s",
keyid_str, error_str);
} else {
T("Bad signature without issuer information: %s",
error_str);
}
}
cookie->bad_checksums ++;
break;
default:
assert (! "reachable");
}
free (keyid_str);
pgp_signature_free (sig);
free (error_str);
pgp_error_free (error);
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);
return PGP_STATUS_SUCCESS;
}
/**
* @internal
*
* <!-- inspect_cb() -->
*
* @brief inspect packet
*
* @param[in,out] cookie_opaque cookie to add result information to
* (in this case, filename information if
* it exists in the packet)
* @param[in] pp pgp_packet_parser_t
*
* @todo More
*/
static pgp_status_t inspect_cb(
void *cookie_opaque, pgp_packet_parser_t pp)
{
struct decrypt_cookie *cookie = cookie_opaque;
pgp_packet_t packet = pgp_packet_parser_packet(pp);
assert(packet);
pgp_tag_t tag = pgp_packet_tag(packet);
T("%s", pgp_tag_to_string(tag));
if (tag == PGP_TAG_LITERAL) {
pgp_literal_t literal = pgp_packet_ref_literal(packet);
cookie->filename = pgp_literal_filename(literal);
pgp_literal_free(literal);
}
pgp_packet_free(packet);
return 0;
}
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, 0, 0, 0, 0, 0, 0, 0, 0, NULL };
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, session->policy, reader,
get_public_keys_cb, decrypt_cb,
check_signatures_cb, inspect_cb,
&cookie, 0);
if (! decryptor) {
if (cookie.bad_passphrase)
status = PEP_WRONG_PASSPHRASE;
else if (cookie.missing_passphrase)
status = PEP_PASSPHRASE_REQUIRED;
else
status = PEP_DECRYPT_NO_KEY;
ERROR_OUT(err, status, "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;
cookie.signer_keylist = NULL; /* Moved. */
stringlist_append(*keylist, cookie.recipient_keylist);
if (filename_ptr) {
*filename_ptr = cookie.filename;
cookie.filename = NULL; /* Moved. */
}
out:
if (status == PEP_STATUS_OK) {
// **********************************
// Sync changes with pgp_verify_text.
// **********************************
if (cookie.good_checksums) {
// If there is at least one signature that we can verify,
// succeed.
status = PEP_DECRYPTED_AND_VERIFIED;
} else if (cookie.revoked_key) {
// If there are any signatures from revoked keys, fail.
status = PEP_VERIFY_SIGNER_KEY_REVOKED;
} else if (cookie.expired_key) {
// If there are any signatures from expired keys, fail.
status = PEP_DECRYPTED;
} else if (cookie.bad_key) {
// If there are any signatures from invalid keys (keys
// that are not signing capable), fail.
status = PEP_DECRYPTED;
} else if (cookie.bad_checksums) {
// If there are any bad signatures, fail.
status = PEP_DECRYPT_SIGNATURE_DOES_NOT_MATCH;
} else {
// We couldn't verify any signatures (possibly because we
// don't have the keys).
status = PEP_DECRYPTED;
}
} else {
free(*ptext);
*ptext = NULL;
}
free_stringlist(cookie.recipient_keylist);
free_stringlist(cookie.signer_keylist);
free(cookie.filename);
pgp_reader_free(reader);
pgp_reader_free(decryptor);
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;
if (size == 0 || sig_size == 0)
return PEP_DECRYPT_WRONG_FORMAT;
#if TRACING > 0
{
int cr = 0;
int crlf = 0;
int lf = 0;
for (int i = 0; i < size; i ++) {
// CR
if (text[i] == '\r') {
cr ++;
}
// LF
if (text[i] == '\n') {
if (i > 0 && text[i - 1] == '\r') {
cr --;
crlf ++;
} else {
lf ++;
}
}
}
T("Text to verify: %zd bytes with %d crlfs, %d bare crs and %d bare lfs",
size, crlf, cr, lf);
}
#endif
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) {
pgp_detached_verifier_t verifier
= pgp_detached_verifier_new(&err, session->policy,
dsig_reader,
get_public_keys_cb,
check_signatures_cb,
NULL,
&cookie, 0);
if (! verifier)
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Creating verifier");
pgp_status_t pgp_status = pgp_detached_verifier_verify (&err, verifier, reader);
pgp_detached_verifier_free (verifier);
if (pgp_status)
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Verifying data");
} else {
pgp_reader_t verifier = NULL;
verifier = pgp_verifier_new(&err, session->policy, reader,
get_public_keys_cb,
check_signatures_cb,
NULL,
&cookie, 0);
if (! verifier)
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Creating verifier");
pgp_status_t pgp_status = pgp_reader_discard(&err, verifier);
pgp_reader_free(verifier);
if (pgp_status)
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;
cookie.signer_keylist = NULL; /* Moved. */
stringlist_append(*keylist, cookie.recipient_keylist);
out:
if (status == PEP_STATUS_OK) {
// *****************************************
// Sync changes with pgp_decrypt_and_verify.
// *****************************************
if (cookie.good_checksums) {
// If there is at least one signature that we can verify,
// succeed.
status = PEP_VERIFIED;
} else if (cookie.revoked_key) {
// If there are any signatures from revoked keys, fail.
status = PEP_VERIFY_SIGNER_KEY_REVOKED;
} else if (cookie.expired_key) {
// If there are any signatures from expired keys, fail.
status = PEP_DECRYPTED;
} else if (cookie.bad_key) {
// If there are any signatures from invalid keys (keys
// that are not signing capable), fail.
status = PEP_DECRYPTED;
} else if (cookie.bad_checksums) {
// If there are any bad signatures, fail.
status = PEP_DECRYPT_SIGNATURE_DOES_NOT_MATCH;
} else {
// We couldn't verify any signatures (possibly because we
// don't have the keys).
status = PEP_UNENCRYPTED;
}
}
free_stringlist(cookie.recipient_keylist);
free_stringlist(cookie.signer_keylist);
pgp_reader_free(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_cert_t signer_cert = NULL;
pgp_cert_valid_key_iter_t iter = NULL;
pgp_valid_key_amalgamation_t ka = NULL;
pgp_key_pair_t signing_keypair = NULL;
pgp_signer_t signer = NULL;
pgp_writer_stack_t ws = NULL;
status = cert_find_by_fpr_hex(session, fpr, true, &signer_cert, NULL);
ERROR_OUT(NULL, status, "Looking up key '%s'", fpr);
iter = pgp_cert_valid_key_iter(signer_cert, session->policy, 0);
pgp_cert_valid_key_iter_alive(iter);
pgp_cert_valid_key_iter_revoked(iter, false);
pgp_cert_valid_key_iter_for_signing (iter);
pgp_key_t key = NULL;
status = _pgp_get_decrypted_key_iter(session, iter, &key);
if (!key || status != PEP_STATUS_OK) {
ERROR_OUT (err, status,
"%s has no signing capable key", fpr);
}
signing_keypair = pgp_key_into_key_pair (NULL, pgp_key_clone (key));
pgp_key_free (key);
if (! signing_keypair)
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "Creating a keypair");
signer = pgp_key_pair_as_signer (signing_keypair);
signing_keypair = NULL;
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_signer_new_detached consumes signer.
signer = NULL;
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");
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");
pgp_status = pgp_armor_writer_finalize (&err, writer);
if (pgp_status != 0)
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Flushing armor writer");
// 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:
pgp_signer_free (signer);
pgp_key_pair_free (signing_keypair);
pgp_valid_key_amalgamation_free (ka);
pgp_cert_valid_key_iter_free (iter);
pgp_cert_free(signer_cert);
T("(%s)-> %s", fpr, pEp_status_to_string(status));
return status;
}
/**
* @internal
*
* <!-- pgp_encrypt_sign_optional() -->
*
* @brief internal function used by pgp_encrypt_only() and
* pgp_encrypt_and_sign() to do encryption, and, where
* indication, signing of the input text
*
* @param[in] session session handle
* @param[in] keylist const stringlist_t*
* @param[in] ptext const char*
* @param[in] psize size_t
* @param[in,out] ctext char**
* @param[in,out] csize size_t*
* @param[in] sign bool
*
* @see pgp_encrypt_only()
* @see pgp_encrypt_and_sign()
*/
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 recipient_cert_count = 0;
pgp_cert_t *recipient_certs = NULL;
int recipient_count = 0;
int recipient_alloc = 0;
pgp_recipient_t *recipients = NULL;
int recipient_keys_count = 0;
pgp_key_t *recipient_keys = NULL;
pgp_cert_t signer_cert = NULL;
pgp_writer_stack_t ws = NULL;
pgp_cert_valid_key_iter_t iter = NULL;
pgp_valid_key_amalgamation_t ka = 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;
int keylist_len = stringlist_length(keylist);
// We don't need to worry about extending recipient_certs, because
// there will be at most KEYLIST_LEN certs, which we allocate up
// front.
recipient_certs = calloc(keylist_len, sizeof(*recipient_certs));
if (recipient_certs == NULL)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
// Because there may be multiple encryption keys per certificate, we may
// need to extend recipient_keys and recipients.
recipient_alloc = keylist_len;
recipient_keys = calloc(recipient_alloc, sizeof(*recipient_keys));
if (recipient_keys == NULL)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
recipients = calloc(recipient_alloc, sizeof(*recipients));
if (recipients == 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_cert_t cert;
status = cert_find_by_fpr_hex(session, _keylist->value,
false, &cert, NULL);
// We couldn't find a key for this recipient.
ERROR_OUT(NULL, status,
"Looking up key for recipient '%s'", _keylist->value);
recipient_certs[recipient_cert_count ++] = cert;
// Collect all of the keys that have the encryption for
// transport capability.
iter = pgp_cert_valid_key_iter(cert, session->policy, 0);
if (! iter)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
pgp_cert_valid_key_iter_alive(iter);
pgp_cert_valid_key_iter_revoked(iter, false);
pgp_cert_valid_key_iter_for_transport_encryption(iter);
while ((ka = pgp_cert_valid_key_iter_next (iter, NULL, NULL))) {
assert(recipient_count == recipient_keys_count);
if (recipient_count == recipient_alloc) {
assert(recipient_alloc > 0);
recipient_alloc *= 2;
void *t = _pEp_reallocarray(recipient_keys, recipient_alloc,
sizeof(*recipient_keys));
if (! t)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
recipient_keys = t;
t = _pEp_reallocarray(recipients, recipient_alloc,
sizeof(*recipients));
if (! t)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
recipients = t;
}
// pgp_valid_key_amalgamation_key returns a reference to
// ka. We need to keep it around after this iteration.
// So, we clone it. Unfortunately, although
// pgp_recipient_new consumes the passed key id, it only
// references the key. So, we need to remember to free it
// at the end.
pgp_key_t key = pgp_valid_key_amalgamation_key (ka);
recipient_keys[recipient_keys_count ++] = pgp_key_clone (key);
pgp_key_free (key);
pgp_keyid_t keyid = pgp_key_keyid(recipient_keys[recipient_keys_count - 1]);
if (! keyid)
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
recipients[recipient_count++] = pgp_recipient_new(keyid, recipient_keys[recipient_keys_count - 1]);
pgp_valid_key_amalgamation_free (ka);
}
pgp_cert_valid_key_iter_free(iter);
iter = NULL;
}
if (sign) {
// The first key in the keylist is the signer.
status = cert_find_by_fpr_hex(session, keylist->value, true, &signer_cert, NULL);
ERROR_OUT(NULL, status, "Looking up key for signing '%s'", keylist->value);
}
pgp_writer_t writer_alloc = pgp_writer_alloc((void **) ctext, csize);
pgp_writer_t writer = pgp_armor_writer_new(&err, writer_alloc,
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, recipients, recipient_count,
0);
// pgp_encrypt_new consumes the recipients (but not the keys).
// This seems to still happen even if it failed, so we need to be sure
// not to try to free them if we bail.
recipient_count = 0;
if (!ws)
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Setting up encryptor");
if (sign) {
iter = pgp_cert_valid_key_iter(signer_cert, session->policy, 0);
pgp_cert_valid_key_iter_alive(iter);
pgp_cert_valid_key_iter_revoked(iter, false);
pgp_cert_valid_key_iter_for_signing (iter);
pgp_key_t key = NULL;
status = _pgp_get_decrypted_key_iter(session, iter, &key);
if (!key || status != PEP_STATUS_OK) {
ERROR_OUT (err, status,
"no signing capable key");
}
signing_keypair = pgp_key_into_key_pair (NULL, key);
if (! signing_keypair)
ERROR_OUT (err, PEP_UNKNOWN_ERROR, "Creating a keypair");
signer = pgp_key_pair_as_signer (signing_keypair);
signing_keypair = NULL;
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");
// pgp_signer_new consumes signer.
signer = NULL;
}
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");
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");
pgp_status = pgp_armor_writer_finalize (&err, writer);
if (pgp_status != 0)
ERROR_OUT(err, PEP_UNKNOWN_ERROR, "Flushing armor writer");
pgp_writer_free (writer_alloc);
// Add a terminating NUL for naive users
void *t = realloc(*ctext, *csize + 1);
if (! t) {
free(*ctext);
*ctext = NULL;
ERROR_OUT(NULL, PEP_OUT_OF_MEMORY, "out of memory");
}
*ctext = t;
(*ctext)[*csize] = 0;
out:
pgp_signer_free (signer);
pgp_key_pair_free (signing_keypair);
pgp_valid_key_amalgamation_free (ka);
pgp_cert_valid_key_iter_free (iter);
pgp_cert_free(signer_cert);
// if we're out of mem, any of these could be in an inconsistent state.
// We're going to bail from above anyway.
if (status != PEP_OUT_OF_MEMORY) {
for (int i = 0; i < recipient_count; i ++)
pgp_recipient_free(recipients[i]);
free(recipients);
for (int i = 0; i < recipient_keys_count; i ++)
pgp_key_free(recipient_keys[i]);
free(recipient_keys);
for (int i = 0; i < recipient_cert_count; i ++)
pgp_cert_free(recipient_certs[i]);
free(recipient_certs);
}
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);