summaryrefslogtreecommitdiffstats
path: root/comm/third_party/libotr/src/privkey.c
diff options
context:
space:
mode:
Diffstat (limited to 'comm/third_party/libotr/src/privkey.c')
-rw-r--r--comm/third_party/libotr/src/privkey.c938
1 files changed, 938 insertions, 0 deletions
diff --git a/comm/third_party/libotr/src/privkey.c b/comm/third_party/libotr/src/privkey.c
new file mode 100644
index 0000000000..6e4bbe40fc
--- /dev/null
+++ b/comm/third_party/libotr/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;
+}
+