summary refs log tree commit diff stats
path: root/libotr/libotr-4.1.1/src/privkey.c
diff options
context:
space:
mode:
Diffstat (limited to 'libotr/libotr-4.1.1/src/privkey.c')
-rw-r--r--libotr/libotr-4.1.1/src/privkey.c938
1 files changed, 938 insertions, 0 deletions
diff --git a/libotr/libotr-4.1.1/src/privkey.c b/libotr/libotr-4.1.1/src/privkey.c
new file mode 100644
index 0000000..6e4bbe4
--- /dev/null
+++ b/libotr/libotr-4.1.1/src/privkey.c
@@ -0,0 +1,938 @@
+/*
+ *  Off-the-Record Messaging library
+ *  Copyright (C) 2004-2012  Ian Goldberg, Rob Smits, Chris Alexander,
+ *  			      Willy Lew, Lisa Du, Nikita Borisov
+ *                           <otr@cypherpunks.ca>
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of version 2.1 of the GNU Lesser General
+ *  Public License as published by the Free Software Foundation.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* system headers */
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+/* libgcrypt headers */
+#include <gcrypt.h>
+
+/* libotr headers */
+#include "privkey.h"
+#include "serial.h"
+
+/* Convert a 20-byte hash value to a 45-byte human-readable value */
+void otrl_privkey_hash_to_human(
+	char human[OTRL_PRIVKEY_FPRINT_HUMAN_LEN],
+	const unsigned char hash[20])
+{
+    int word, byte;
+    char *p = human;
+
+    for(word=0; word<5; ++word) {
+	for(byte=0; byte<4; ++byte) {
+	    sprintf(p, "%02X", hash[word*4+byte]);
+	    p += 2;
+	}
+	*(p++) = ' ';
+    }
+    /* Change that last ' ' to a '\0' */
+    --p;
+    *p = '\0';
+}
+
+/* Calculate a human-readable hash of our DSA public key.  Return it in
+ * the passed fingerprint buffer.  Return NULL on error, or a pointer to
+ * the given buffer on success. */
+char *otrl_privkey_fingerprint(OtrlUserState us,
+	char fingerprint[OTRL_PRIVKEY_FPRINT_HUMAN_LEN],
+	const char *accountname, const char *protocol)
+{
+    unsigned char hash[20];
+    OtrlPrivKey *p = otrl_privkey_find(us, accountname, protocol);
+
+    if (p) {
+	/* Calculate the hash */
+	gcry_md_hash_buffer(GCRY_MD_SHA1, hash, p->pubkey_data,
+		p->pubkey_datalen);
+
+	/* Now convert it to a human-readable format */
+	otrl_privkey_hash_to_human(fingerprint, hash);
+    } else {
+	return NULL;
+    }
+
+    return fingerprint;
+}
+
+/* Calculate a raw hash of our DSA public key.  Return it in the passed
+ * fingerprint buffer.  Return NULL on error, or a pointer to the given
+ * buffer on success. */
+unsigned char *otrl_privkey_fingerprint_raw(OtrlUserState us,
+	unsigned char hash[20], const char *accountname, const char *protocol)
+{
+    OtrlPrivKey *p = otrl_privkey_find(us, accountname, protocol);
+
+    if (p) {
+	/* Calculate the hash */
+	gcry_md_hash_buffer(GCRY_MD_SHA1, hash, p->pubkey_data,
+		p->pubkey_datalen);
+    } else {
+	return NULL;
+    }
+
+    return hash;
+}
+
+/* Create a public key block from a private key */
+static gcry_error_t make_pubkey(unsigned char **pubbufp, size_t *publenp,
+	gcry_sexp_t privkey)
+{
+    gcry_mpi_t p,q,g,y;
+    gcry_sexp_t dsas,ps,qs,gs,ys;
+    size_t np,nq,ng,ny;
+    enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+    unsigned char *bufp;
+    size_t lenp;
+
+    *pubbufp = NULL;
+    *publenp = 0;
+
+    /* Extract the public parameters */
+    dsas = gcry_sexp_find_token(privkey, "dsa", 0);
+    if (dsas == NULL) {
+	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+    }
+    ps = gcry_sexp_find_token(dsas, "p", 0);
+    qs = gcry_sexp_find_token(dsas, "q", 0);
+    gs = gcry_sexp_find_token(dsas, "g", 0);
+    ys = gcry_sexp_find_token(dsas, "y", 0);
+    gcry_sexp_release(dsas);
+    if (!ps || !qs || !gs || !ys) {
+	gcry_sexp_release(ps);
+	gcry_sexp_release(qs);
+	gcry_sexp_release(gs);
+	gcry_sexp_release(ys);
+	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+    }
+    p = gcry_sexp_nth_mpi(ps, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(ps);
+    q = gcry_sexp_nth_mpi(qs, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(qs);
+    g = gcry_sexp_nth_mpi(gs, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(gs);
+    y = gcry_sexp_nth_mpi(ys, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(ys);
+    if (!p || !q || !g || !y) {
+	gcry_mpi_release(p);
+	gcry_mpi_release(q);
+	gcry_mpi_release(g);
+	gcry_mpi_release(y);
+	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+    }
+
+    *publenp = 0;
+    gcry_mpi_print(format, NULL, 0, &np, p);
+    *publenp += np + 4;
+    gcry_mpi_print(format, NULL, 0, &nq, q);
+    *publenp += nq + 4;
+    gcry_mpi_print(format, NULL, 0, &ng, g);
+    *publenp += ng + 4;
+    gcry_mpi_print(format, NULL, 0, &ny, y);
+    *publenp += ny + 4;
+
+    *pubbufp = malloc(*publenp);
+    if (*pubbufp == NULL) {
+	gcry_mpi_release(p);
+	gcry_mpi_release(q);
+	gcry_mpi_release(g);
+	gcry_mpi_release(y);
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    bufp = *pubbufp;
+    lenp = *publenp;
+
+    write_mpi(p,np,"P");
+    write_mpi(q,nq,"Q");
+    write_mpi(g,ng,"G");
+    write_mpi(y,ny,"Y");
+
+    gcry_mpi_release(p);
+    gcry_mpi_release(q);
+    gcry_mpi_release(g);
+    gcry_mpi_release(y);
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* Read a sets of private DSA keys from a file on disk into the given
+ * OtrlUserState. */
+gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename)
+{
+    FILE *privf;
+    gcry_error_t err;
+
+    /* Open the privkey file.  We use rb mode so that on WIN32, fread()
+     * reads the same number of bytes that fstat() indicates are in the
+     * file. */
+    privf = fopen(filename, "rb");
+    if (!privf) {
+	err = gcry_error_from_errno(errno);
+	return err;
+    }
+
+    err = otrl_privkey_read_FILEp(us, privf);
+
+    fclose(privf);
+    return err;
+}
+
+/* Read a sets of private DSA keys from a FILE* into the given
+ * OtrlUserState.  The FILE* must be open for reading. */
+gcry_error_t otrl_privkey_read_FILEp(OtrlUserState us, FILE *privf)
+{
+    int privfd;
+    struct stat st;
+    char *buf;
+    const char *token;
+    size_t tokenlen;
+    gcry_error_t err;
+    gcry_sexp_t allkeys;
+    int i;
+
+    if (!privf) return gcry_error(GPG_ERR_NO_ERROR);
+
+    /* Release any old ideas we had about our keys */
+    otrl_privkey_forget_all(us);
+
+    /* Load the data into a buffer */
+    privfd = fileno(privf);
+    if (fstat(privfd, &st)) {
+	err = gcry_error_from_errno(errno);
+	return err;
+    }
+    buf = malloc(st.st_size);
+    if (!buf && st.st_size > 0) {
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    if (fread(buf, st.st_size, 1, privf) != 1) {
+	err = gcry_error_from_errno(errno);
+	free(buf);
+	return err;
+    }
+
+    err = gcry_sexp_new(&allkeys, buf, st.st_size, 0);
+    free(buf);
+    if (err) {
+	return err;
+    }
+
+    token = gcry_sexp_nth_data(allkeys, 0, &tokenlen);
+    if (tokenlen != 8 || strncmp(token, "privkeys", 8)) {
+	gcry_sexp_release(allkeys);
+	return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+    }
+
+    /* Get each account */
+    for(i=1; i<gcry_sexp_length(allkeys); ++i) {
+	gcry_sexp_t names, protos, privs;
+	char *name, *proto;
+	gcry_sexp_t accounts;
+	OtrlPrivKey *p;
+
+	/* Get the ith "account" S-exp */
+	accounts = gcry_sexp_nth(allkeys, i);
+
+	/* It's really an "account" S-exp? */
+	token = gcry_sexp_nth_data(accounts, 0, &tokenlen);
+	if (tokenlen != 7 || strncmp(token, "account", 7)) {
+	    gcry_sexp_release(accounts);
+	    gcry_sexp_release(allkeys);
+	    return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+	}
+	/* Extract the name, protocol, and privkey S-exps */
+	names = gcry_sexp_find_token(accounts, "name", 0);
+	protos = gcry_sexp_find_token(accounts, "protocol", 0);
+	privs = gcry_sexp_find_token(accounts, "private-key", 0);
+	gcry_sexp_release(accounts);
+	if (!names || !protos || !privs) {
+	    gcry_sexp_release(names);
+	    gcry_sexp_release(protos);
+	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
+	    return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+	}
+	/* Extract the actual name and protocol */
+	token = gcry_sexp_nth_data(names, 1, &tokenlen);
+	if (!token) {
+	    gcry_sexp_release(names);
+	    gcry_sexp_release(protos);
+	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
+	    return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+	}
+	name = malloc(tokenlen + 1);
+	if (!name) {
+	    gcry_sexp_release(names);
+	    gcry_sexp_release(protos);
+	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
+	    return gcry_error(GPG_ERR_ENOMEM);
+	}
+	memmove(name, token, tokenlen);
+	name[tokenlen] = '\0';
+	gcry_sexp_release(names);
+
+	token = gcry_sexp_nth_data(protos, 1, &tokenlen);
+	if (!token) {
+	    free(name);
+	    gcry_sexp_release(protos);
+	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
+	    return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+	}
+	proto = malloc(tokenlen + 1);
+	if (!proto) {
+	    free(name);
+	    gcry_sexp_release(protos);
+	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
+	    return gcry_error(GPG_ERR_ENOMEM);
+	}
+	memmove(proto, token, tokenlen);
+	proto[tokenlen] = '\0';
+	gcry_sexp_release(protos);
+
+	/* Make a new OtrlPrivKey entry */
+	p = malloc(sizeof(*p));
+	if (!p) {
+	    free(name);
+	    free(proto);
+	    gcry_sexp_release(privs);
+	    gcry_sexp_release(allkeys);
+	    return gcry_error(GPG_ERR_ENOMEM);
+	}
+
+	/* Fill it in and link it up */
+	p->accountname = name;
+	p->protocol = proto;
+	p->pubkey_type = OTRL_PUBKEY_TYPE_DSA;
+	p->privkey = privs;
+	p->next = us->privkey_root;
+	if (p->next) {
+	    p->next->tous = &(p->next);
+	}
+	p->tous = &(us->privkey_root);
+	us->privkey_root = p;
+	err = make_pubkey(&(p->pubkey_data), &(p->pubkey_datalen), p->privkey);
+	if (err) {
+	    gcry_sexp_release(allkeys);
+	    otrl_privkey_forget(p);
+	    return gcry_error(GPG_ERR_UNUSABLE_SECKEY);
+	}
+    }
+    gcry_sexp_release(allkeys);
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+static OtrlPendingPrivKey *pending_find(OtrlUserState us,
+	const char *accountname, const char *protocol)
+{
+    OtrlPendingPrivKey *search = us->pending_root;
+
+    while (search) {
+	if (!strcmp(search->accountname, accountname) &&
+		!strcmp(search->protocol, protocol)) {
+	    /* Found it */
+	    return search;
+	}
+	search = search->next;
+    }
+    return NULL;
+}
+
+/* Insert an account/protocol pair into the pending privkey list of the
+ * given OtrlUserState and return a pointer to the new
+ * OtrlPendingPrivKey, or return NULL if it's already there. */
+static OtrlPendingPrivKey *pending_insert(OtrlUserState us,
+	const char *accountname, const char *protocol)
+{
+    /* See if it's already there */
+    OtrlPendingPrivKey *search = pending_find(us, accountname, protocol);
+
+    if (search) {
+	/* It is */
+	return NULL;
+    }
+
+    /* We'll insert it at the beginning of the list */
+    search = malloc(sizeof(*search));
+    if (!search) return NULL;
+
+    search->accountname = strdup(accountname);
+    search->protocol = strdup(protocol);
+
+    search->next = us->pending_root;
+    us->pending_root = search;
+    if (search->next) {
+	search->next->tous = &(search->next);
+    }
+    search->tous = &(us->pending_root);
+    return search;
+}
+
+static void pending_forget(OtrlPendingPrivKey *ppk)
+{
+    if (ppk) {
+	free(ppk->accountname);
+	free(ppk->protocol);
+
+	/* Re-link the list */
+	*(ppk->tous) = ppk->next;
+	if (ppk->next) {
+	    ppk->next->tous = ppk->tous;
+	}
+
+	free(ppk);
+    }
+}
+
+/* Free the memory associated with the pending privkey list */
+void otrl_privkey_pending_forget_all(OtrlUserState us)
+{
+    while(us->pending_root) {
+	pending_forget(us->pending_root);
+    }
+}
+
+static gcry_error_t sexp_write(FILE *privf, gcry_sexp_t sexp)
+{
+    size_t buflen;
+    char *buf;
+
+    buflen = gcry_sexp_sprint(sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
+    buf = malloc(buflen);
+    if (buf == NULL && buflen > 0) {
+	return gcry_error(GPG_ERR_ENOMEM);
+    }
+    gcry_sexp_sprint(sexp, GCRYSEXP_FMT_ADVANCED, buf, buflen);
+
+    fprintf(privf, "%s", buf);
+    free(buf);
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+static gcry_error_t account_write(FILE *privf, const char *accountname,
+	const char *protocol, gcry_sexp_t privkey)
+{
+    gcry_error_t err;
+    gcry_sexp_t names, protos;
+
+    fprintf(privf, " (account\n");
+
+    err = gcry_sexp_build(&names, NULL, "(name %s)", accountname);
+    if (!err) {
+	err = sexp_write(privf, names);
+	gcry_sexp_release(names);
+    }
+    if (!err) err = gcry_sexp_build(&protos, NULL, "(protocol %s)", protocol);
+    if (!err) {
+	err = sexp_write(privf, protos);
+	gcry_sexp_release(protos);
+    }
+    if (!err) err = sexp_write(privf, privkey);
+
+    fprintf(privf, " )\n");
+
+    return err;
+}
+
+struct s_pending_privkey_calc {
+    char *accountname;
+    char *protocol;
+    gcry_sexp_t privkey;
+};
+
+/* Begin a private key generation that will potentially take place in
+ * a background thread.  This routine must be called from the main
+ * thread.  It will set *newkeyp, which you can pass to
+ * otrl_privkey_generate_calculate in a background thread.  If it
+ * returns gcry_error(GPG_ERR_EEXIST), then a privkey creation for
+ * this accountname/protocol is already in progress, and *newkeyp will
+ * be set to NULL. */
+gcry_error_t otrl_privkey_generate_start(OtrlUserState us,
+	const char *accountname, const char *protocol, void **newkeyp)
+{
+    OtrlPendingPrivKey *found = pending_find(us, accountname, protocol);
+    struct s_pending_privkey_calc *ppc;
+
+    if (found) {
+	if (newkeyp) *newkeyp = NULL;
+	return gcry_error(GPG_ERR_EEXIST);
+    }
+
+    /* We're not already creating this key.  Mark it as in progress. */
+    pending_insert(us, accountname, protocol);
+
+    /* Allocate the working structure */
+    ppc = malloc(sizeof(*ppc));
+    ppc->accountname = strdup(accountname);
+    ppc->protocol = strdup(protocol);
+    ppc->privkey = NULL;
+
+    *newkeyp = ppc;
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* Do the private key generation calculation.  You may call this from a
+ * background thread.  When it completes, call
+ * otrl_privkey_generate_finish from the _main_ thread. */
+gcry_error_t otrl_privkey_generate_calculate(void *newkey)
+{
+    struct s_pending_privkey_calc *ppc =
+	    (struct s_pending_privkey_calc *)newkey;
+    gcry_error_t err;
+    gcry_sexp_t key, parms;
+    static const char *parmstr = "(genkey (dsa (nbits 4:1024)))";
+
+    /* Create a DSA key */
+    err = gcry_sexp_new(&parms, parmstr, strlen(parmstr), 0);
+    if (err) {
+	return err;
+    }
+    err = gcry_pk_genkey(&key, parms);
+    gcry_sexp_release(parms);
+    if (err) {
+	return err;
+    }
+
+    /* Extract the privkey */
+    ppc->privkey = gcry_sexp_find_token(key, "private-key", 0);
+    gcry_sexp_release(key);
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+static FILE* privkey_fopen(const char *filename, gcry_error_t *errp)
+{
+    FILE *privf;
+#ifndef WIN32
+    mode_t oldmask;
+#endif
+
+#ifndef WIN32
+    oldmask = umask(077);
+#endif
+    privf = fopen(filename, "w+b");
+    if (!privf && errp) {
+	*errp = gcry_error_from_errno(errno);
+    }
+#ifndef WIN32
+    umask(oldmask);
+#endif
+    return privf;
+}
+
+/* Call this from the main thread only, in the event that the background
+ * thread generating the key is cancelled.  The newkey is deallocated,
+ * and must not be used further. */
+void otrl_privkey_generate_cancelled(OtrlUserState us, void *newkey)
+{
+    struct s_pending_privkey_calc *ppc =
+	    (struct s_pending_privkey_calc *)newkey;
+
+    if (us) {
+	pending_forget(pending_find(us, ppc->accountname, ppc->protocol));
+    }
+
+    /* Deallocate ppc */
+    free(ppc->accountname);
+    free(ppc->protocol);
+    gcry_sexp_release(ppc->privkey);
+    free(ppc);
+}
+
+/* Call this from the main thread only.  It will write the newly created
+ * private key into the given file and store it in the OtrlUserState. */
+gcry_error_t otrl_privkey_generate_finish(OtrlUserState us,
+	void *newkey, const char *filename)
+{
+    gcry_error_t err;
+    FILE *privf = privkey_fopen(filename, &err);
+    if (!privf) {
+	return err;
+    }
+
+    err = otrl_privkey_generate_finish_FILEp(us, newkey, privf);
+
+    fclose(privf);
+    return err;
+}
+
+/* Call this from the main thread only.  It will write the newly created
+ * private key into the given FILE* (which must be open for reading and
+ * writing) and store it in the OtrlUserState. */
+gcry_error_t otrl_privkey_generate_finish_FILEp(OtrlUserState us,
+	void *newkey, FILE *privf)
+{
+    struct s_pending_privkey_calc *ppc =
+	    (struct s_pending_privkey_calc *)newkey;
+    gcry_error_t ret = gcry_error(GPG_ERR_INV_VALUE);
+
+    if (ppc && us && privf) {
+	OtrlPrivKey *p;
+
+	/* Output the other keys we know */
+	fprintf(privf, "(privkeys\n");
+
+	for (p=us->privkey_root; p; p=p->next) {
+	    /* Skip this one if our new key replaces it */
+	    if (!strcmp(p->accountname, ppc->accountname) &&
+		    !strcmp(p->protocol, ppc->protocol)) {
+		continue;
+	    }
+
+	    account_write(privf, p->accountname, p->protocol, p->privkey);
+	}
+	account_write(privf, ppc->accountname, ppc->protocol, ppc->privkey);
+	fprintf(privf, ")\n");
+
+	fseek(privf, 0, SEEK_SET);
+
+	ret = otrl_privkey_read_FILEp(us, privf);
+    }
+
+    otrl_privkey_generate_cancelled(us, newkey);
+
+    return ret;
+}
+
+/* Generate a private DSA key for a given account, storing it into a
+ * file on disk, and loading it into the given OtrlUserState.  Overwrite any
+ * previously generated keys for that account in that OtrlUserState. */
+gcry_error_t otrl_privkey_generate(OtrlUserState us, const char *filename,
+	const char *accountname, const char *protocol)
+{
+    gcry_error_t err;
+    FILE *privf = privkey_fopen(filename, &err);
+    if (!privf) {
+	return err;
+    }
+
+    err = otrl_privkey_generate_FILEp(us, privf, accountname, protocol);
+
+    fclose(privf);
+    return err;
+}
+
+/* Generate a private DSA key for a given account, storing it into a
+ * FILE*, and loading it into the given OtrlUserState.  Overwrite any
+ * previously generated keys for that account in that OtrlUserState.
+ * The FILE* must be open for reading and writing. */
+gcry_error_t otrl_privkey_generate_FILEp(OtrlUserState us, FILE *privf,
+	const char *accountname, const char *protocol)
+{
+    void *newkey = NULL;
+    gcry_error_t err;
+
+    err = otrl_privkey_generate_start(us, accountname, protocol, &newkey);
+    if (newkey) {
+	otrl_privkey_generate_calculate(newkey);
+	err = otrl_privkey_generate_finish_FILEp(us, newkey, privf);
+    }
+
+    return err;
+}
+
+/* Convert a hex character to a value */
+static unsigned int ctoh(char c)
+{
+    if (c >= '0' && c <= '9') return c-'0';
+    if (c >= 'a' && c <= 'f') return c-'a'+10;
+    if (c >= 'A' && c <= 'F') return c-'A'+10;
+    return 0;  /* Unknown hex char */
+}
+
+/* Read the fingerprint store from a file on disk into the given
+ * OtrlUserState.  Use add_app_data to add application data to each
+ * ConnContext so created. */
+gcry_error_t otrl_privkey_read_fingerprints(OtrlUserState us,
+	const char *filename,
+	void (*add_app_data)(void *data, ConnContext *context),
+	void  *data)
+{
+    gcry_error_t err;
+    FILE *storef;
+
+    storef = fopen(filename, "rb");
+    if (!storef) {
+	err = gcry_error_from_errno(errno);
+	return err;
+    }
+
+    err = otrl_privkey_read_fingerprints_FILEp(us, storef, add_app_data, data);
+
+    fclose(storef);
+    return err;
+}
+
+/* Read the fingerprint store from a FILE* into the given
+ * OtrlUserState.  Use add_app_data to add application data to each
+ * ConnContext so created.  The FILE* must be open for reading. */
+gcry_error_t otrl_privkey_read_fingerprints_FILEp(OtrlUserState us,
+	FILE *storef,
+	void (*add_app_data)(void *data, ConnContext *context),
+	void  *data)
+{
+    ConnContext *context;
+    char storeline[1000];
+    unsigned char fingerprint[20];
+    size_t maxsize = sizeof(storeline);
+
+    if (!storef) return gcry_error(GPG_ERR_NO_ERROR);
+
+    while(fgets(storeline, maxsize, storef)) {
+	char *username;
+	char *accountname;
+	char *protocol;
+	char *hex;
+	char *trust;
+	char *tab;
+	char *eol;
+	Fingerprint *fng;
+	int i, j;
+	/* Parse the line, which should be of the form:
+	 *    username\taccountname\tprotocol\t40_hex_nybbles\n          */
+	username = storeline;
+	tab = strchr(username, '\t');
+	if (!tab) continue;
+	*tab = '\0';
+
+	accountname = tab + 1;
+	tab = strchr(accountname, '\t');
+	if (!tab) continue;
+	*tab = '\0';
+
+	protocol = tab + 1;
+	tab = strchr(protocol, '\t');
+	if (!tab) continue;
+	*tab = '\0';
+
+	hex = tab + 1;
+	tab = strchr(hex, '\t');
+	if (!tab) {
+	    eol = strchr(hex, '\r');
+	    if (!eol) eol = strchr(hex, '\n');
+	    if (!eol) continue;
+	    *eol = '\0';
+	    trust = NULL;
+	} else {
+	    *tab = '\0';
+	    trust = tab + 1;
+	    eol = strchr(trust, '\r');
+	    if (!eol) eol = strchr(trust, '\n');
+	    if (!eol) continue;
+	    *eol = '\0';
+	}
+
+	if (strlen(hex) != 40) continue;
+	for(j=0, i=0; i<40; i+=2) {
+	    fingerprint[j++] = (ctoh(hex[i]) << 4) + (ctoh(hex[i+1]));
+	}
+	/* Get the context for this user, adding if not yet present */
+	context = otrl_context_find(us, username, accountname, protocol,
+		OTRL_INSTAG_MASTER, 1, NULL, add_app_data, data);
+	/* Add the fingerprint if not already there */
+	fng = otrl_context_find_fingerprint(context, fingerprint, 1, NULL);
+	otrl_context_set_trust(fng, trust);
+    }
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* Write the fingerprint store from a given OtrlUserState to a file on disk. */
+gcry_error_t otrl_privkey_write_fingerprints(OtrlUserState us,
+	const char *filename)
+{
+    gcry_error_t err;
+    FILE *storef;
+
+    storef = fopen(filename, "wb");
+    if (!storef) {
+	err = gcry_error_from_errno(errno);
+	return err;
+    }
+
+    err = otrl_privkey_write_fingerprints_FILEp(us, storef);
+
+    fclose(storef);
+    return err;
+}
+
+/* Write the fingerprint store from a given OtrlUserState to a FILE*.
+ * The FILE* must be open for writing. */
+gcry_error_t otrl_privkey_write_fingerprints_FILEp(OtrlUserState us,
+	FILE *storef)
+{
+    ConnContext *context;
+    Fingerprint *fprint;
+
+    if (!storef) return gcry_error(GPG_ERR_NO_ERROR);
+
+    for(context = us->context_root; context; context = context->next) {
+	/* Fingerprints are only stored in the master contexts */
+	if (context->their_instance != OTRL_INSTAG_MASTER) continue;
+
+	/* Don't bother with the first (fingerprintless) entry. */
+	for (fprint = context->fingerprint_root.next; fprint;
+		fprint = fprint->next) {
+	    int i;
+	    fprintf(storef, "%s\t%s\t%s\t", context->username,
+		    context->accountname, context->protocol);
+	    for(i=0;i<20;++i) {
+		fprintf(storef, "%02x", fprint->fingerprint[i]);
+	    }
+	    fprintf(storef, "\t%s\n", fprint->trust ? fprint->trust : "");
+	}
+    }
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* Fetch the private key from the given OtrlUserState associated with
+ * the given account */
+OtrlPrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname,
+	const char *protocol)
+{
+    OtrlPrivKey *p;
+    if (!accountname || !protocol) return NULL;
+
+    for(p=us->privkey_root; p; p=p->next) {
+	if (!strcmp(p->accountname, accountname) &&
+		!strcmp(p->protocol, protocol)) {
+	    return p;
+	}
+    }
+    return NULL;
+}
+
+/* Forget a private key */
+void otrl_privkey_forget(OtrlPrivKey *privkey)
+{
+    free(privkey->accountname);
+    free(privkey->protocol);
+    gcry_sexp_release(privkey->privkey);
+    free(privkey->pubkey_data);
+
+    /* Re-link the list */
+    *(privkey->tous) = privkey->next;
+    if (privkey->next) {
+	privkey->next->tous = privkey->tous;
+    }
+
+    /* Free the privkey struct */
+    free(privkey);
+}
+
+/* Forget all private keys in a given OtrlUserState. */
+void otrl_privkey_forget_all(OtrlUserState us)
+{
+    while (us->privkey_root) {
+	otrl_privkey_forget(us->privkey_root);
+    }
+}
+
+/* Sign data using a private key.  The data must be small enough to be
+ * signed (i.e. already hashed, if necessary).  The signature will be
+ * returned in *sigp, which the caller must free().  Its length will be
+ * returned in *siglenp. */
+gcry_error_t otrl_privkey_sign(unsigned char **sigp, size_t *siglenp,
+	OtrlPrivKey *privkey, const unsigned char *data, size_t len)
+{
+    gcry_mpi_t r,s, datampi;
+    gcry_sexp_t dsas, rs, ss, sigs, datas;
+    size_t nr, ns;
+    const enum gcry_mpi_format format = GCRYMPI_FMT_USG;
+
+    if (privkey->pubkey_type != OTRL_PUBKEY_TYPE_DSA)
+	return gcry_error(GPG_ERR_INV_VALUE);
+
+    *sigp = malloc(40);
+    if (*sigp == NULL) return gcry_error(GPG_ERR_ENOMEM);
+    *siglenp = 40;
+
+    if (len) {
+	gcry_mpi_scan(&datampi, GCRYMPI_FMT_USG, data, len, NULL);
+    } else {
+	datampi = gcry_mpi_set_ui(NULL, 0);
+    }
+    gcry_sexp_build(&datas, NULL, "(%m)", datampi);
+    gcry_mpi_release(datampi);
+    gcry_pk_sign(&sigs, datas, privkey->privkey);
+    gcry_sexp_release(datas);
+    dsas = gcry_sexp_find_token(sigs, "dsa", 0);
+    gcry_sexp_release(sigs);
+    rs = gcry_sexp_find_token(dsas, "r", 0);
+    ss = gcry_sexp_find_token(dsas, "s", 0);
+    gcry_sexp_release(dsas);
+    r = gcry_sexp_nth_mpi(rs, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(rs);
+    s = gcry_sexp_nth_mpi(ss, 1, GCRYMPI_FMT_USG);
+    gcry_sexp_release(ss);
+    gcry_mpi_print(format, NULL, 0, &nr, r);
+    gcry_mpi_print(format, NULL, 0, &ns, s);
+    memset(*sigp, 0, 40);
+    gcry_mpi_print(format, (*sigp)+(20-nr), nr, NULL, r);
+    gcry_mpi_print(format, (*sigp)+20+(20-ns), ns, NULL, s);
+    gcry_mpi_release(r);
+    gcry_mpi_release(s);
+
+    return gcry_error(GPG_ERR_NO_ERROR);
+}
+
+/* Verify a signature on data using a public key.  The data must be
+ * small enough to be signed (i.e. already hashed, if necessary). */
+gcry_error_t otrl_privkey_verify(const unsigned char *sigbuf, size_t siglen,
+	unsigned short pubkey_type, gcry_sexp_t pubs,
+	const unsigned char *data, size_t len)
+{
+    gcry_error_t err;
+    gcry_mpi_t datampi,r,s;
+    gcry_sexp_t datas, sigs;
+
+    if (pubkey_type != OTRL_PUBKEY_TYPE_DSA || siglen != 40)
+	return gcry_error(GPG_ERR_INV_VALUE);
+
+    if (len) {
+	gcry_mpi_scan(&datampi, GCRYMPI_FMT_USG, data, len, NULL);
+    } else {
+	datampi = gcry_mpi_set_ui(NULL, 0);
+    }
+    gcry_sexp_build(&datas, NULL, "(%m)", datampi);
+    gcry_mpi_release(datampi);
+    gcry_mpi_scan(&r, GCRYMPI_FMT_USG, sigbuf, 20, NULL);
+    gcry_mpi_scan(&s, GCRYMPI_FMT_USG, sigbuf+20, 20, NULL);
+    gcry_sexp_build(&sigs, NULL, "(sig-val (dsa (r %m)(s %m)))", r, s);
+    gcry_mpi_release(r);
+    gcry_mpi_release(s);
+
+    err = gcry_pk_verify(sigs, datas, pubs);
+    gcry_sexp_release(datas);
+    gcry_sexp_release(sigs);
+
+    return err;
+}
+