You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4285 lines
144 KiB
C
4285 lines
144 KiB
C
/**
|
|
* @internal
|
|
* @file pgp_sequoia.c
|
|
* @brief Implementation of functions used to speak to Sequoia-PGP backend
|
|
*/
|
|
|
|
|
|
#if defined (__clang__)
|
|
# pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
|
#endif
|
|
|
|
#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, ¤t, 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);
|
|
}
|
|
|
|
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 |