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.
 
 
 
 

1655 lines
39 KiB

#include "pEp_internal.h"
#include "pgp_netpgp.h"
#include <limits.h>
#include "wrappers.h"
#include "netpgp.h"
#include <netpgp/config.h>
#include <netpgp/memory.h>
#include <netpgp/crypto.h>
#include <netpgp/netpgpsdk.h>
#include <netpgp/validate.h>
#include <netpgp/readerwriter.h>
#include <curl/curl.h>
#include <pthread.h>
#include <regex.h>
static netpgp_t netpgp;
static pthread_mutex_t netpgp_mutex;
static PEP_STATUS init_netpgp()
{
PEP_STATUS status = PEP_STATUS_OK;
const char *home = NULL;
if(pthread_mutex_init(&netpgp_mutex, NULL)){
return PEP_OUT_OF_MEMORY;
}
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
if (strcmp(setlocale(LC_ALL, NULL), "C") == 0)
setlocale(LC_ALL, "");
memset(&netpgp, 0x0, sizeof(netpgp_t));
// netpgp_setvar(&netpgp, "max mem alloc", "4194304");
netpgp_setvar(&netpgp, "need seckey", "1");
// netpgp_setvar(&netpgp, "need userid", "1");
// NetPGP shares home with GPG
home = gpg_home();
if(home){
netpgp_set_homedir(&netpgp,(char*)home, NULL, 0);
}else{
status = PEP_INIT_NO_GPG_HOME;
goto unlock_netpgp;
}
// pair with gpg's cert-digest-algo
netpgp_setvar(&netpgp, "hash", "SHA256");
// subset of gpg's personal-cipher-preferences
// here only one cipher can be selected
netpgp_setvar(&netpgp, "cipher", "CAST5");
if (!netpgp_init(&netpgp)) {
status = PEP_INIT_NETPGP_INIT_FAILED;
goto unlock_netpgp;
}
// netpgp_set_debug("packet-parse.c");
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return status;
}
static void release_netpgp()
{
if(pthread_mutex_lock(&netpgp_mutex)){
return;
}
netpgp_end(&netpgp);
memset(&netpgp, 0x0, sizeof(netpgp_t));
pthread_mutex_destroy(&netpgp_mutex);
return;
}
static PEP_STATUS init_curl(
pthread_mutex_t *curl_mutex,
bool in_first)
{
PEP_STATUS status = PEP_STATUS_OK;
if(pthread_mutex_init(curl_mutex, NULL)){
return PEP_OUT_OF_MEMORY;
}
if(pthread_mutex_lock(curl_mutex)){
return PEP_UNKNOWN_ERROR;
}
if(in_first){
curl_global_init(CURL_GLOBAL_DEFAULT);
}
pthread_mutex_unlock(curl_mutex);
return status;
}
static void release_curl(
pthread_mutex_t *curl_mutex,
bool out_last)
{
if(pthread_mutex_lock(curl_mutex)){
return;
}
if(out_last){
curl_global_cleanup();
}
pthread_mutex_destroy(curl_mutex);
return;
}
static PEP_STATUS curl_get_ctx(
CURL **curl)
{
PEP_STATUS status = PEP_STATUS_OK;
struct curl_slist *headers=NULL;
if ((*curl = curl_easy_init()) == NULL) {
return PEP_OUT_OF_MEMORY;
}
curl_easy_setopt(*curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(*curl, CURLOPT_MAXREDIRS, 3L);
headers=curl_slist_append(headers,"Pragma: no-cache");
if(headers)
headers=curl_slist_append(headers,"Cache-Control: no-cache");
if(!headers)
{
return PEP_OUT_OF_MEMORY;
}
curl_easy_setopt(curl,CURLOPT_HTTPHEADER,headers);
curl_slist_free_all(headers);
// TODO curl_easy_setopt(curl,CURLOPT_PROXY,proxy);
return status;
}
static void curl_release_ctx(
CURL **curl)
{
if(*curl)
curl_easy_cleanup(*curl);
*curl = NULL;
return;
}
PEP_STATUS pgp_init(PEP_SESSION session, bool in_first)
{
PEP_STATUS status = PEP_STATUS_OK;
assert(session);
if(!session) return PEP_ILLEGAL_VALUE;
if (in_first) {
if((status = init_netpgp()) != PEP_STATUS_OK)
return status;
}
if((status = init_curl(
&session->ctx.curl_mutex,
in_first) != PEP_STATUS_OK)){
if(in_first) release_netpgp();
return status;
}
return PEP_STATUS_OK;
}
void pgp_release(PEP_SESSION session, bool out_last)
{
assert(session);
if(!session) return;
if (out_last){
release_netpgp();
}
release_curl(&session->ctx.curl_mutex, out_last);
}
// return 1 if the file contains ascii-armoured text
static unsigned
_armoured(const char *buf, size_t size, const char *pattern)
{
unsigned armoured = 0;
regex_t r;
regcomp(&r, pattern, REG_EXTENDED|REG_NOSUB);
if (regnexec(&r, buf, size, 0, NULL, 0) == 0) {
armoured = 1;
}
regfree(&r);
return armoured;
}
/* write key fingerprint hexdump as a string */
static unsigned
fpr_to_str (char **str, const uint8_t *fpr, size_t length)
{
unsigned i;
int n;
/* 4 hexes per short + null */
*str = malloc((length / 2) * 4 + 1);
if(*str == NULL)
return 0;
for (n = 0, i = 0 ; i < length; i += 2) {
n += snprintf(&((*str)[n]), 5, "%02x%02x", fpr[i], fpr[i+1]);
}
return 1;
}
/* write key fingerprint bytes read from hex string
* accept spaces and hexes */
static unsigned
str_to_fpr (const char *str, uint8_t *fpr, size_t *length)
{
unsigned i,j;
*length = 0;
while(*str && *length < PGP_FINGERPRINT_SIZE){
while (*str == ' ') str++;
for (j = 0; j < 2; j++) {
uint8_t *byte = &fpr[*length];
*byte = 0;
for (i = 0; i < 2; i++) {
if (i > 0)
*byte = *byte << 4;
if (*str >= 'a' && *str <= 'f')
*byte += 10 + *str - 'a';
else if (*str >= 'A' && *str <= 'F')
*byte += 10 + *str - 'A';
else if (*str >= '0' && *str <= '9')
*byte += *str - '0';
else
return 0;
str++;
}
(*length)++;
}
}
return 1;
}
// Iterate through netpgp' reported valid signatures
// fill a list of valid figerprints
// returns PEP_STATUS_OK if all sig reported valid
// error status otherwise.
static PEP_STATUS _validation_results(
netpgp_t *netpgp,
pgp_validation_t *vresult,
stringlist_t **_keylist
)
{
time_t now;
time_t t;
now = time(NULL);
if (now < vresult->birthtime) {
// signature is not valid yet
return PEP_DECRYPT_SIGNATURE_DOES_NOT_MATCH;
}
if (vresult->duration != 0 && now > vresult->birthtime + vresult->duration) {
// signature has expired
t = vresult->duration + vresult->birthtime;
return PEP_DECRYPT_SIGNATURE_DOES_NOT_MATCH;
}
if (vresult->validc && vresult->valid_sigs &&
!vresult->invalidc && !vresult->unknownc ) {
unsigned n;
stringlist_t *k;
// caller responsible to free
*_keylist = new_stringlist(NULL);
assert(*_keylist);
if (*_keylist == NULL) {
return PEP_OUT_OF_MEMORY;
}
k = *_keylist;
for (n = 0; n < vresult->validc; ++n) {
unsigned from = 0;
const pgp_key_t *signer;
char *fprstr = NULL;
const uint8_t *keyid = vresult->valid_sigs[n].signer_id;
signer = pgp_getkeybyid(netpgp->io, netpgp->pubring,
keyid, &from, NULL, NULL,
0, 0); /* check neither revocation nor expiry
as is should be checked already */
if(signer)
fpr_to_str(&fprstr,
signer->pubkeyfpr.fingerprint,
signer->pubkeyfpr.length);
else
return PEP_VERIFY_NO_KEY;
if (fprstr == NULL)
return PEP_OUT_OF_MEMORY;
k = stringlist_add(k, fprstr);
free(fprstr);
if(!k){
free_stringlist(*_keylist);
return PEP_OUT_OF_MEMORY;
}
}
return PEP_STATUS_OK;
}
if (vresult->validc + vresult->invalidc + vresult->unknownc == 0) {
// No signatures found - is this memory signed?
return PEP_VERIFY_NO_KEY;
}
if (vresult->invalidc) {
// some invalid signatures
return PEP_DECRYPT_SIGNATURE_DOES_NOT_MATCH;
}
// only unknown sigs
return PEP_DECRYPTED;
}
#define _ENDL "\\s*(\r\n|\r|\n)"
#define ARMOR_HEAD "^-----BEGIN PGP MESSAGE-----"_ENDL
PEP_STATUS pgp_decrypt_and_verify(
PEP_SESSION session, const char *ctext, size_t csize,
char **ptext, size_t *psize, stringlist_t **keylist
)
{
pgp_memory_t *mem;
pgp_validation_t *vresult;
char *_ptext = NULL;
size_t _psize = 0;
PEP_STATUS result;
stringlist_t *_keylist = NULL;
assert(session);
assert(ctext);
assert(csize);
assert(ptext);
assert(psize);
assert(keylist);
if(!session || !ctext || !csize || !ptext || !psize || !keylist)
return PEP_ILLEGAL_VALUE;
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
*ptext = NULL;
*psize = 0;
*keylist = NULL;
vresult = malloc(sizeof(pgp_validation_t));
memset(vresult, 0x0, sizeof(pgp_validation_t));
mem = pgp_decrypt_and_validate_buf(netpgp.io, vresult, ctext, csize,
netpgp.secring, netpgp.pubring,
_armoured(ctext, csize, ARMOR_HEAD),
0 /* sshkeys */,
NULL, -1, NULL /* pass fp,attempts,cb */);
if (mem == NULL) {
result = PEP_OUT_OF_MEMORY;
goto unlock_netpgp;
}
_psize = pgp_mem_len(mem);
if (_psize){
if ((_ptext = malloc(_psize + 1)) == NULL) {
result = PEP_OUT_OF_MEMORY;
goto free_pgp;
}
memcpy(_ptext, pgp_mem_data(mem), _psize);
_ptext[_psize] = '\0'; // safeguard for naive users
result = PEP_DECRYPTED;
}else{
result = PEP_DECRYPT_NO_KEY;
goto free_pgp;
}
if (result == PEP_DECRYPTED) {
result = _validation_results(&netpgp, vresult, &_keylist);
if (result == PEP_DECRYPTED) {
//no change
} else if (result == PEP_VERIFY_NO_KEY) {
result = PEP_DECRYPTED;
}else if (result != PEP_STATUS_OK) {
goto free_ptext;
}else{
result = PEP_DECRYPTED_AND_VERIFIED;
}
}
if (result == PEP_DECRYPTED_AND_VERIFIED
|| result == PEP_DECRYPTED) {
*ptext = _ptext;
*psize = _psize;
(*ptext)[*psize] = 0; // safeguard for naive users
if (result == PEP_DECRYPTED_AND_VERIFIED) {
*keylist = _keylist;
}
/* _ptext and _keylist ownership transfer, don't free */
goto free_pgp;
}
free_keylist:
free_stringlist(_keylist);
free_ptext:
free(_ptext);
free_pgp:
pgp_memory_free(mem);
pgp_validate_result_free(vresult);
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return result;
}
#define ARMOR_SIG_HEAD "^-----BEGIN PGP (SIGNATURE|SIGNED MESSAGE)-----"_ENDL
PEP_STATUS pgp_verify_text(
PEP_SESSION session, const char *text, size_t size,
const char *signature, size_t sig_size, stringlist_t **keylist
)
{
pgp_memory_t *signedmem;
pgp_memory_t *sig;
pgp_validation_t *vresult;
PEP_STATUS result;
stringlist_t *_keylist;
assert(session);
assert(text);
assert(size);
assert(signature);
assert(sig_size);
assert(keylist);
if(!session || !text || !size || !signature || !sig_size || !keylist)
return PEP_ILLEGAL_VALUE;
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
*keylist = NULL;
vresult = malloc(sizeof(pgp_validation_t));
if (vresult == NULL) {
result = PEP_OUT_OF_MEMORY;
goto unlock_netpgp;
}
memset(vresult, 0x0, sizeof(pgp_validation_t));
signedmem = pgp_memory_new();
if (signedmem == NULL) {
result = PEP_OUT_OF_MEMORY;
goto unlock_netpgp;
}
pgp_memory_add(signedmem, (const uint8_t*)text, size);
sig = pgp_memory_new();
if (sig == NULL) {
pgp_memory_free(signedmem);
result = PEP_OUT_OF_MEMORY;
goto unlock_netpgp;
}
pgp_memory_add(sig, (const uint8_t*)signature, sig_size);
pgp_validate_mem_detached(netpgp.io, vresult, sig,
NULL,/* output */
_armoured(signature, sig_size, ARMOR_SIG_HEAD),
netpgp.pubring,
signedmem);
result = _validation_results(&netpgp, vresult, &_keylist);
if (result != PEP_STATUS_OK) {
goto free_pgp;
}else{
result = PEP_VERIFIED;
}
if (result == PEP_VERIFIED) {
/* TODO : check trust level */
result = PEP_VERIFIED_AND_TRUSTED;
}
if (result == PEP_VERIFIED || result == PEP_VERIFIED_AND_TRUSTED) {
*keylist = _keylist;
/* _keylist ownership transfer, don't free */
goto free_pgp;
}
free_keylist:
free_stringlist(_keylist);
free_pgp:
// free done by pgp_validate_mem_detached
// pgp_memory_free(sig);
// pgp_memory_free(signedmem);
pgp_validate_result_free(vresult);
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return result;
}
PEP_STATUS pgp_encrypt_and_sign(
PEP_SESSION session, const stringlist_t *keylist, const char *ptext,
size_t psize, char **ctext, size_t *csize
)
{
pgp_key_t *signer = NULL;
pgp_seckey_t *seckey = NULL;
pgp_memory_t *signedmem;
pgp_memory_t *cmem;
const char *hashalg;
pgp_keyring_t *rcpts;
PEP_STATUS result;
const stringlist_t *_keylist;
assert(session);
assert(keylist);
assert(ptext);
assert(psize);
assert(ctext);
assert(csize);
if(!session || !ptext || !psize || !ctext || !csize || !keylist)
return PEP_ILLEGAL_VALUE;
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
*ctext = NULL;
*csize = 0;
if ((rcpts = calloc(1, sizeof(*rcpts))) == NULL) {
result = PEP_OUT_OF_MEMORY;
goto unlock_netpgp;
}
for (_keylist = keylist; _keylist != NULL; _keylist = _keylist->next) {
assert(_keylist->value);
const pgp_key_t *key;
uint8_t fpr[PGP_FINGERPRINT_SIZE];
size_t fprlen;
unsigned from = 0;
if (str_to_fpr(_keylist->value, fpr, &fprlen)) {
if ((key = (pgp_key_t *)pgp_getkeybyfpr(netpgp.io, netpgp.pubring,
fpr, fprlen, &from, NULL,
/* reject revoked, accept expired */
1,0)) == NULL) {
result = PEP_KEY_NOT_FOUND;
goto free_rcpts;
}
}else{
result = PEP_ILLEGAL_VALUE;
goto free_rcpts;
}
/* Signer is the first key in the list */
if(signer == NULL){
from = 0;
signer = (pgp_key_t *)pgp_getkeybyfpr(netpgp.io, netpgp.secring,
fpr, fprlen,
&from,
NULL,
0,0); /* accept any */
if(signer == NULL){
result = PEP_KEY_NOT_FOUND;
goto free_rcpts;
}
}
// add key to recipients/signers
pgp_keyring_add(rcpts, key);
if(rcpts->keys == NULL){
result = PEP_OUT_OF_MEMORY;
goto free_rcpts;
}
}
/* Empty keylist ?*/
if(rcpts->keyc == 0){
result = PEP_ILLEGAL_VALUE;
goto free_rcpts;
}
seckey = pgp_key_get_certkey(signer);
/* No signig key. Revoked ? */
if(seckey == NULL){
result = PEP_GET_KEY_FAILED;
goto free_rcpts;
}
hashalg = netpgp_getvar(&netpgp, "hash");
// Sign data
signedmem = pgp_sign_buf(netpgp.io, ptext, psize, seckey,
time(NULL), /* birthtime */
0 /* duration */,
hashalg,
0 /* armored */,
0 /* cleartext */);
if (!signedmem) {
result = PEP_UNENCRYPTED;
goto free_rcpts;
}
// Encrypt signed data
cmem = pgp_encrypt_buf(netpgp.io, pgp_mem_data(signedmem),
pgp_mem_len(signedmem), rcpts, 1 /* armored */,
netpgp_getvar(&netpgp, "cipher"),
1 /* takes raw OpenPGP message */);
if (cmem == NULL) {
result = PEP_OUT_OF_MEMORY;
goto free_signedmem;
}else{
char *_buffer = NULL;
size_t length = pgp_mem_len(cmem);
// Allocate transferable buffer
_buffer = malloc(length + 1);
assert(_buffer);
if (_buffer == NULL) {
result = PEP_OUT_OF_MEMORY;
goto free_cmem;
}
memcpy(_buffer, pgp_mem_data(cmem), length);
*ctext = _buffer;
*csize = length;
(*ctext)[*csize] = 0; // safeguard for naive users
result = PEP_STATUS_OK;
}
free_cmem :
pgp_memory_free(cmem);
free_signedmem :
pgp_memory_free(signedmem);
free_rcpts :
pgp_keyring_free(rcpts);
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return result;
}
PEP_STATUS pgp_generate_keypair(
PEP_SESSION session, pEp_identity *identity
)
{
pgp_key_t newseckey;
pgp_key_t *newpubkey;
PEP_STATUS result;
char newid[1024];
const char *hashalg;
const char *cipher;
assert(session);
assert(identity);
assert(identity->address);
assert(identity->fpr == NULL);
assert(identity->username);
if(!session || !identity ||
!identity->address || identity->fpr || !identity->username)
return PEP_ILLEGAL_VALUE;
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
if(snprintf(newid, sizeof(newid),
"%s <%s>", identity->username, identity->address) >= sizeof(newid)){
result = PEP_BUFFER_TOO_SMALL;
goto unlock_netpgp;
}
hashalg = netpgp_getvar(&netpgp, "hash");
cipher = netpgp_getvar(&netpgp, "cipher");
bzero(&newseckey, sizeof(newseckey));
// Generate the key
if (!pgp_rsa_generate_keypair(&newseckey, 4096, 65537UL, hashalg, cipher,
(const uint8_t *) "", (const size_t) 0))
{
result = PEP_CANNOT_CREATE_KEY;
goto free_seckey;
}
/* make a public key out of generated secret key */
if((newpubkey = pgp_ensure_pubkey(
netpgp.pubring,
&newseckey.key.seckey.pubkey,
newseckey.pubkeyid))==NULL)
{
result = PEP_OUT_OF_MEMORY;
goto free_seckey;
}
// "Expire-Date: 1y\n";
if (!pgp_add_selfsigned_userid(&newseckey, newpubkey,
(uint8_t *)newid, 365*24*3600))
{
result = PEP_CANNOT_CREATE_KEY;
goto delete_pubkey;
}
if (newpubkey == NULL)
{
result = PEP_OUT_OF_MEMORY;
goto delete_pubkey;
}
// Append key to netpgp's rings (key ownership transfered)
if (!pgp_keyring_add(netpgp.secring, &newseckey)){
result = PEP_OUT_OF_MEMORY;
goto delete_pubkey;
}
// save rings
if (netpgp_save_pubring(&netpgp) && netpgp_save_secring(&netpgp))
{
char *fprstr = NULL;
fpr_to_str(&fprstr,
newseckey.pubkeyfpr.fingerprint,
newseckey.pubkeyfpr.length);
if (fprstr == NULL) {
result = PEP_OUT_OF_MEMORY;
goto pop_secring;
}
/* keys saved, pass fingerprint back */
identity->fpr = fprstr;
result = PEP_STATUS_OK;
/* free nothing, everything transfered */
goto unlock_netpgp;
} else {
/* XXX in case only pubring save succeed
* pubring file is left as-is, but backup restore
* could be attempted if such corner case matters */
result = PEP_UNKNOWN_ERROR;
}
pop_secring:
((pgp_keyring_t *)netpgp.secring)->keyc--;
delete_pubkey:
pgp_deletekeybyfpr(netpgp.io,
(pgp_keyring_t *)netpgp.pubring,
newseckey.pubkeyfpr.fingerprint,
newseckey.pubkeyfpr.length);
free_seckey:
pgp_key_free(&newseckey);
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return result;
}
PEP_STATUS pgp_delete_keypair(PEP_SESSION session, const char *fprstr)
{
uint8_t fpr[PGP_FINGERPRINT_SIZE];
size_t length;
PEP_STATUS result;
assert(session);
assert(fprstr);
if (!session || !fprstr)
return PEP_ILLEGAL_VALUE;
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
if (str_to_fpr(fprstr, fpr, &length)) {
unsigned insec = pgp_deletekeybyfpr(netpgp.io,
(pgp_keyring_t *)netpgp.secring,
(const uint8_t *)fpr, length);
unsigned inpub = pgp_deletekeybyfpr(netpgp.io,
(pgp_keyring_t *)netpgp.pubring,
(const uint8_t *)fpr, length);
if(!insec && !inpub){
result = PEP_KEY_NOT_FOUND;
goto unlock_netpgp;
} else {
result = PEP_STATUS_OK;
}
}else{
result = PEP_OUT_OF_MEMORY;
goto unlock_netpgp;
}
// save rings
if (netpgp_save_pubring(&netpgp) &&
netpgp_save_secring(&netpgp))
{
result = PEP_STATUS_OK;
}else{
result = PEP_UNKNOWN_ERROR;
}
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return result;
}
#define ARMOR_KEY_HEAD "^-----BEGIN PGP (PUBLIC|PRIVATE) KEY BLOCK-----"_ENDL
PEP_STATUS pgp_import_keydata(
PEP_SESSION session,
const char *key_data,
size_t size
)
{
pgp_memory_t *mem;
PEP_STATUS result = PEP_STATUS_OK;
assert(session);
assert(key_data);
if(!session || !key_data)
return PEP_ILLEGAL_VALUE;
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
mem = pgp_memory_new();
if (mem == NULL) {
result = PEP_OUT_OF_MEMORY;
goto unlock_netpgp;
}
pgp_memory_add(mem, (const uint8_t*)key_data, size);
if (pgp_keyring_read_from_mem(netpgp.io, netpgp.pubring, netpgp.secring,
_armoured(key_data, size, ARMOR_KEY_HEAD),
mem) == 0){
result = PEP_ILLEGAL_VALUE;
}
pgp_memory_free(mem);
// save rings
if (netpgp_save_pubring(&netpgp) &&
netpgp_save_secring(&netpgp))
{
result = PEP_STATUS_OK;
}else{
result = PEP_UNKNOWN_ERROR;
}
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return result;
}
static PEP_STATUS _export_keydata(
pgp_key_t *key,
char **buffer,
size_t *buflen
)
{
PEP_STATUS result;
pgp_output_t *output;
pgp_memory_t *mem;
pgp_setup_memory_write(&output, &mem, 128);
if (mem == NULL || output == NULL) {
return PEP_ILLEGAL_VALUE;
}
if (!pgp_write_xfer_key(output, key, 1)) {
result = PEP_UNKNOWN_ERROR;
goto free_mem;
}
*buffer = NULL;
*buflen = pgp_mem_len(mem);
// Allocate transferable buffer
*buffer = malloc(*buflen + 1);
assert(*buffer);
if (*buffer == NULL) {
result = PEP_OUT_OF_MEMORY;
goto free_mem;
}
memcpy(*buffer, pgp_mem_data(mem), *buflen);
(*buffer)[*buflen] = 0; // safeguard for naive users
return PEP_STATUS_OK;
free_mem :
pgp_teardown_memory_write(output, mem);
return result;
}
PEP_STATUS pgp_export_keydata(
PEP_SESSION session, const char *fprstr, char **key_data, size_t *size
)
{
pgp_key_t *key;
uint8_t fpr[PGP_FINGERPRINT_SIZE];
size_t fprlen;
PEP_STATUS result;
char *buffer;
size_t buflen;
assert(session);
assert(fprstr);
assert(key_data);
assert(size);
if (!session || !fprstr || !key_data || !size)
return PEP_ILLEGAL_VALUE;
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
if (str_to_fpr(fprstr, fpr, &fprlen)) {
unsigned from = 0;
if ((key = (pgp_key_t *)pgp_getkeybyfpr(netpgp.io, netpgp.pubring,
fpr, fprlen, &from,
NULL,0,0)) == NULL) {
result = PEP_KEY_NOT_FOUND;
goto unlock_netpgp;
}
}else{
result = PEP_OUT_OF_MEMORY;
goto unlock_netpgp;
}
result = _export_keydata(key, &buffer, &buflen);
if(result == PEP_STATUS_OK)
{
*key_data = buffer;
*size = buflen;
result = PEP_STATUS_OK;
}
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return result;
}
struct HKP_answer {
char *memory;
size_t size;
};
static size_t
HKPAnswerWriter(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct HKP_answer *mem = (struct HKP_answer *)userp;
mem->memory = realloc(mem->memory, mem->size + realsize + 1);
if(mem->memory == NULL) {
mem->size = 0;
return 0;
}
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
#define HKP_SERVER "http://keys.gnupg.net:11371"
// #define HKP_SERVER "http://127.0.0.1:11371"
PEP_STATUS pgp_recv_key(PEP_SESSION session, const char *pattern)
{
static const char *ks_cmd = HKP_SERVER
"/pks/lookup?"
"op=get&options=mr&exact=on&"
"search=";
char *encoded_pattern;
char *request = NULL;
struct HKP_answer answer;
CURLcode curlres;
PEP_STATUS result;
CURL *curl;
assert(session);
assert(pattern);
if (!session || !pattern )
return PEP_ILLEGAL_VALUE;
if(pthread_mutex_lock(&session->ctx.curl_mutex)){
return PEP_UNKNOWN_ERROR;
}
result = curl_get_ctx(&curl);
if(result != PEP_STATUS_OK){
goto unlock_curl;
}
encoded_pattern = curl_easy_escape(curl, (char*)pattern, 0);
if(!encoded_pattern){
result = PEP_OUT_OF_MEMORY;
goto release_curl_ctx;
}
if((request = malloc(strlen(ks_cmd) + strlen(encoded_pattern) + 1))==NULL){
result = PEP_OUT_OF_MEMORY;
goto free_encoded_pattern;
}
//(*stpcpy(stpcpy(request, ks_cmd), encoded_pattern)) = '\0';
stpcpy(stpcpy(request, ks_cmd), encoded_pattern);
curl_easy_setopt(curl, CURLOPT_URL,request);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HKPAnswerWriter);
answer.memory = NULL;
answer.size = 0;
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&answer);
curlres = curl_easy_perform(curl);
if(curlres != CURLE_OK) {
result = PEP_GET_KEY_FAILED;
goto free_request;
}
if(!answer.memory || !answer.size) {
result = PEP_OUT_OF_MEMORY;
goto free_request;
}
result = pgp_import_keydata(session,
answer.memory,
answer.size);
free_answer:
free(answer.memory);
free_request:
free(request);
free_encoded_pattern:
curl_free(encoded_pattern);
release_curl_ctx:
curl_release_ctx(&curl);
unlock_curl:
pthread_mutex_unlock(&session->ctx.curl_mutex);
return result;
}
typedef PEP_STATUS (*find_key_cb_t)(void*, pgp_key_t *);
static PEP_STATUS find_keys_do(
const char *pattern, find_key_cb_t cb, void* cb_arg)
{
uint8_t fpr[PGP_FINGERPRINT_SIZE];
size_t length;
pgp_key_t *key;
PEP_STATUS result;
// Try find a fingerprint in pattern
if (str_to_fpr(pattern, fpr, &length)) {
unsigned from = 0;
// Only one fingerprint can match
if ((key = (pgp_key_t *)pgp_getkeybyfpr(
netpgp.io,
(pgp_keyring_t *)netpgp.pubring,
(const uint8_t *)fpr, length,
&from,
NULL, 0, 0)) == NULL) {
return PEP_KEY_NOT_FOUND;
}
result = cb(cb_arg, key);
} else {
// Search by name for pattern. Can match many.
unsigned from = 0;
result = PEP_KEY_NOT_FOUND;
while((key = (pgp_key_t *)pgp_getnextkeybyname(
netpgp.io,
(pgp_keyring_t *)netpgp.pubring,
(const char *)pattern,
&from)) != NULL) {
result = cb(cb_arg, key);
if (result != PEP_STATUS_OK)
break;
from++;
}
}
return result;
}
static PEP_STATUS add_key_fpr_to_stringlist(void *arg, pgp_key_t *key)
{
stringlist_t **keylist = arg;
char *newfprstr = NULL;
fpr_to_str(&newfprstr,
key->pubkeyfpr.fingerprint,
key->pubkeyfpr.length);
if (newfprstr == NULL) {
return PEP_OUT_OF_MEMORY;
} else {
*keylist = stringlist_add(*keylist, newfprstr);
if (*keylist == NULL) {
free(newfprstr);
return PEP_OUT_OF_MEMORY;
}
}
return PEP_STATUS_OK;
}
PEP_STATUS pgp_find_keys(
PEP_SESSION session, const char *pattern, stringlist_t **keylist
)
{
stringlist_t *_keylist, *_k;
PEP_STATUS result;
assert(session);
assert(pattern);
assert(keylist);
if (!session || !pattern || !keylist )
{
return PEP_ILLEGAL_VALUE;
}
if (pthread_mutex_lock(&netpgp_mutex))
{
return PEP_UNKNOWN_ERROR;
}
*keylist = NULL;
_keylist = new_stringlist(NULL);
if (_keylist == NULL) {
result = PEP_OUT_OF_MEMORY;
goto unlock_netpgp;
}
_k = _keylist;
result = find_keys_do(pattern, &add_key_fpr_to_stringlist, &_k);
if (result == PEP_STATUS_OK) {
*keylist = _keylist;
// Transfer ownership, no free
goto unlock_netpgp;
}
free_keylist:
free_stringlist(_keylist);
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return result;
}
#define HKP_REQ_PREFIX "keytext="
#define HKP_REQ_PREFIX_LEN 8
static PEP_STATUS send_key_cb(void *arg, pgp_key_t *key)
{
char *buffer = NULL;
size_t buflen = 0;
PEP_STATUS result;
stringlist_t *encoded_keys;
encoded_keys = (stringlist_t*)arg;
result = _export_keydata(key, &buffer, &buflen);
if(result == PEP_STATUS_OK){
char *encoded_key;
char *request;
size_t encoded_key_len;
encoded_key = curl_escape(buffer, (int)buflen);
if(!encoded_key){
result = PEP_OUT_OF_MEMORY;
goto free_buffer;
}
encoded_key_len = strlen(encoded_key);
request = calloc(1, HKP_REQ_PREFIX_LEN + encoded_key_len + 1);
if(!request){
result = PEP_OUT_OF_MEMORY;
goto free_encoded_key;
}
memcpy(request, HKP_REQ_PREFIX, HKP_REQ_PREFIX_LEN);
memcpy(request + HKP_REQ_PREFIX_LEN, encoded_key, encoded_key_len);
request[HKP_REQ_PREFIX_LEN + encoded_key_len] = '\0';
if(!stringlist_add(encoded_keys, request)){
result = PEP_OUT_OF_MEMORY;
}
free(request);
free_encoded_key:
curl_free(encoded_key);
free_buffer:
free(buffer);
}
return result;
}
PEP_STATUS pgp_send_key(PEP_SESSION session, const char *pattern)
{
static const char *ks_cmd = HKP_SERVER "/pks/add";
stringlist_t *encoded_keys;
const stringlist_t *post;
PEP_STATUS result;
CURL *curl;
assert(session);
assert(pattern);
if (!session || !pattern )
return PEP_ILLEGAL_VALUE;
encoded_keys = new_stringlist(NULL);
assert(encoded_keys);
if (encoded_keys == NULL) {
return PEP_OUT_OF_MEMORY;
}
if(pthread_mutex_lock(&netpgp_mutex)){
result = PEP_UNKNOWN_ERROR;
goto free_encoded_keys;
}
result = find_keys_do(pattern, &send_key_cb, (void*)encoded_keys);
pthread_mutex_unlock(&netpgp_mutex);
if(result != PEP_STATUS_OK){
goto free_encoded_keys;
}
if(pthread_mutex_lock(&session->ctx.curl_mutex)){
result = PEP_UNKNOWN_ERROR;
goto free_encoded_keys;
}
result = curl_get_ctx(&curl);
if(result != PEP_STATUS_OK){
goto unlock_curl;
}
if(result == PEP_STATUS_OK){
CURLcode curlres;
for (post = encoded_keys; post != NULL; post = post->next) {
assert(post->value);
curl_easy_setopt(curl, CURLOPT_URL, ks_cmd);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post->value);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
// Uncomment if debugging
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curlres = curl_easy_perform(curl);
if(curlres != CURLE_OK) {
result = PEP_CANNOT_SEND_KEY;
goto release_curl_ctx;
}
}
}
release_curl_ctx:
curl_release_ctx(&curl);
unlock_curl:
pthread_mutex_unlock(&session->ctx.curl_mutex);
free_encoded_keys:
free_stringlist(encoded_keys);
return result;
}
PEP_STATUS pgp_get_key_rating(
PEP_SESSION session,
const char *fprstr,
PEP_comm_type *comm_type
)
{
pgp_key_t *key;
uint8_t fpr[PGP_FINGERPRINT_SIZE];
unsigned from = 0;
size_t length;
PEP_STATUS status = PEP_STATUS_OK;
assert(session);
assert(fprstr);
assert(comm_type);
if (!session || !fprstr || !comm_type )
return PEP_ILLEGAL_VALUE;
*comm_type = PEP_ct_unknown;
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
if (!str_to_fpr(fprstr, fpr, &length)) {
status = PEP_ILLEGAL_VALUE;
goto unlock_netpgp;
}
key = pgp_getkeybyfpr(
netpgp.io,
netpgp.pubring,
fpr, length, &from, NULL,0,0);
if(key == NULL)
{
status = PEP_KEY_NOT_FOUND;
goto unlock_netpgp;
}
switch(pgp_key_get_rating(key)){
case PGP_VALID:
*comm_type = PEP_ct_OpenPGP_unconfirmed;
break;
case PGP_WEAK:
*comm_type = PEP_ct_OpenPGP_weak_unconfirmed;
break;
case PGP_TOOSHORT:
*comm_type = PEP_ct_key_too_short;
break;
case PGP_INVALID:
*comm_type = PEP_ct_key_b0rken;
break;
case PGP_EXPIRED:
*comm_type = PEP_ct_key_expired;
break;
case PGP_REVOKED:
*comm_type = PEP_ct_key_revoked;
break;
default:
break;
}
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return status;
}
PEP_STATUS pgp_renew_key(
PEP_SESSION session,
const char *fprstr,
const timestamp *ts
)
{
pgp_key_t *pkey;
pgp_key_t *skey;
uint8_t fpr[PGP_FINGERPRINT_SIZE];
size_t length;
unsigned from = 0;
time_t duration;
const uint8_t *primid;
PEP_STATUS status = PEP_STATUS_OK;
assert(session);
assert(fprstr);
if (!session || !fprstr )
return PEP_ILLEGAL_VALUE;
if(ts)
{
time_t now, when;
now = time(NULL);
when = mktime((struct tm*)ts);
if(now && when && when > now){
duration = when - now;
}else{
return PEP_ILLEGAL_VALUE;
}
}else{
/* Default 1 year from now */
duration = 365*24*3600;
}
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
if (!str_to_fpr(fprstr, fpr, &length)) {
status = PEP_ILLEGAL_VALUE;
goto unlock_netpgp;
}
pkey = pgp_getkeybyfpr(
netpgp.io,
netpgp.pubring,
fpr, length, &from, NULL,
1, 0); /* reject revoked, accept expired */
if(pkey == NULL)
{
status = PEP_KEY_NOT_FOUND;
goto unlock_netpgp;
}
from = 0;
skey = pgp_getkeybyfpr(
netpgp.io,
netpgp.secring,
fpr, length, &from, NULL,
1, 0); /* reject revoked, accept expired */
if(skey == NULL)
{
status = PEP_KEY_NOT_FOUND;
goto unlock_netpgp;
}
if((primid = pgp_key_get_primary_userid(skey)) == NULL)
{
status = PEP_KEY_HAS_AMBIG_NAME;
goto unlock_netpgp;
}
// FIXME : renew in a more gentle way
if (!pgp_add_selfsigned_userid(skey, pkey, primid, duration))
{
status = PEP_CANNOT_CREATE_KEY;
goto unlock_netpgp;
}
// save rings
if (netpgp_save_pubring(&netpgp) &&
netpgp_save_secring(&netpgp))
{
status = PEP_STATUS_OK;
}else{
status = PEP_UNKNOWN_ERROR;
}
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return status;
}
PEP_STATUS pgp_revoke_key(
PEP_SESSION session,
const char *fprstr,
const char *reason
)
{
pgp_key_t *pkey;
pgp_key_t *skey;
uint8_t fpr[PGP_FINGERPRINT_SIZE];
size_t length;
unsigned from = 0;
PEP_STATUS status = PEP_STATUS_OK;
assert(session);
assert(fprstr);
if (!session || !fprstr)
return PEP_UNKNOWN_ERROR;
if(pthread_mutex_lock(&netpgp_mutex)){
return PEP_UNKNOWN_ERROR;
}
// FIXME : deduplicate that code w/ renew
if (!str_to_fpr(fprstr, fpr, &length)) {
status = PEP_ILLEGAL_VALUE;
goto unlock_netpgp;
}
pkey = pgp_getkeybyfpr(
netpgp.io,
netpgp.pubring,
fpr, length, &from, NULL,
1, 0); /* reject revoked, accept expired */
if(pkey == NULL)
{
status = PEP_KEY_NOT_FOUND;
goto unlock_netpgp;
}
from = 0;
skey = pgp_getkeybyfpr(
netpgp.io,
netpgp.secring,
fpr, length, &from, NULL,
1, 0); /* reject revoked, accept expired */
if(skey == NULL)
{
status = PEP_KEY_NOT_FOUND;
goto unlock_netpgp;
}
pgp_key_revoke(skey, pkey,
0, /* no reason code specified */
reason);
unlock_netpgp:
pthread_mutex_unlock(&netpgp_mutex);
return status;
}
PEP_STATUS pgp_key_expired(
PEP_SESSION session,
const char *fprstr,
const time_t when,
bool *expired
)
{
PEP_STATUS status = PEP_STATUS_OK;
PEP_comm_type comm_type;
assert(session);
assert(fprstr);
assert(expired);
if (!session || !fprstr || !expired)
return PEP_UNKNOWN_ERROR;
// TODO : take "when" in account
*expired = false;
status = pgp_get_key_rating(session, fprstr, &comm_type);
if (status != PEP_STATUS_OK)
return status;
if (comm_type == PEP_ct_key_expired){
*expired = true;
}
return PEP_STATUS_OK;
}
PEP_STATUS pgp_key_revoked(
PEP_SESSION session,
const char *fprstr,
bool *revoked
)
{
PEP_STATUS status = PEP_STATUS_OK;
PEP_comm_type comm_type;
assert(session);
assert(fprstr);
assert(revoked);
*revoked = false;
status = pgp_get_key_rating(session, fprstr, &comm_type);
if (status != PEP_STATUS_OK)
return status;
if (comm_type == PEP_ct_key_revoked){
*revoked = true;
}
return PEP_STATUS_OK;
}