diff --git a/src/cert-gpgsm.c b/src/cert-gpgsm.c index 6e6f5a5..e8f663b 100644 --- a/src/cert-gpgsm.c +++ b/src/cert-gpgsm.c @@ -1,631 +1,631 @@ /* cert-gpgsm.c - Scute certificate searching. * Copyright (C) 2006, 2007 g10 Code GmbH * * This file is part of Scute. * * Scute is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Scute 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 program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "agent.h" #include "cert.h" #include "support.h" #include "debug.h" /* The maximum length of a key listing line. We take the double of * the allowed Assuan line length plus some extra space to avoid a * memmove after a part of a line has been processed. */ #define MAX_LINE_LEN (ASSUAN_LINELENGTH*2 + 200) struct keylist_ctx { /* The pending line in an active key listing. */ char pending[MAX_LINE_LEN + 1]; unsigned int pending_len; /* The current certificate. */ struct cert cert; /* The caller's search callback, invoked for each certificate. */ cert_search_cb_t search_cb; void *search_cb_hook; }; /* Support macros */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) /*** Local prototypes ***/ static gpg_error_t export_cert (const char *fpr, struct cert *cert); /* Release allocated storage for the certificate CERT and reset the certificate. */ static void cert_reset (struct cert *cert) { if (cert->issuer_serial) free (cert->issuer_serial); if (cert->issuer_name) free (cert->issuer_name); if (cert->uid) free (cert->uid); if (cert->cert_der) free (cert->cert_der); memset (cert, '\0', sizeof (struct cert)); } /* Parse the string TIMESTAMP into a time_t. The string may either be seconds since Epoch or in the ISO 8601 format like "20390815T143012". Returns 0 for an empty string or seconds since Epoch. Leading spaces are skipped. If ENDP is not NULL, it will point to the next non-parsed character in TIMESTRING. */ static time_t parse_timestamp (const char *timestamp, char **endp) { /* Need to skip leading spaces, because that is what strtoul does but not our ISO 8601 checking code. */ while (*timestamp && *timestamp== ' ') timestamp++; if (!*timestamp) return 0; if (strlen (timestamp) >= 15 && timestamp[8] == 'T') { struct tm buf; int year; year = atoi_4 (timestamp); if (year < 1900) return (time_t)(-1); /* Fixme: We would better use a configure test to see whether mktime can handle dates beyond 2038. */ if (sizeof (time_t) <= 4 && year >= 2038) return (time_t)2145914603; /* 2037-12-31 23:23:23 */ memset (&buf, 0, sizeof buf); buf.tm_year = year - 1900; buf.tm_mon = atoi_2 (timestamp+4) - 1; buf.tm_mday = atoi_2 (timestamp+6); buf.tm_hour = atoi_2 (timestamp+9); buf.tm_min = atoi_2 (timestamp+11); buf.tm_sec = atoi_2 (timestamp+13); if (endp) *endp = (char*)(timestamp + 15); #ifdef HAVE_TIMEGM return timegm (&buf); #else /* FIXME: Need to set TZ to UTC, but that is not thread-safe. */ return mktime (&buf); #endif } else return (time_t)strtoul (timestamp, endp, 10); } /* Decode the C formatted string SRC and store the result in the buffer *DESTP which is LEN bytes long. If LEN is zero, then a large enough buffer is allocated with malloc and *DESTP is set to the result. Currently, LEN is only used to specify if allocation is desired or not, the caller is expected to make sure that *DESTP is large enough if LEN is not zero. */ static gpg_error_t decode_c_string (const char *src, char **destp, size_t len) { char *dest; /* Set up the destination buffer. */ if (len) { if (len < strlen (src) + 1) return gpg_error (GPG_ERR_INTERNAL); dest = *destp; } else { /* The converted string will never be larger than the original string. */ dest = malloc (strlen (src) + 1); if (!dest) return gpg_error_from_syserror (); *destp = dest; } /* Convert the string. */ while (*src) { if (*src != '\\') { *(dest++) = *(src++); continue; } switch (src[1]) { #define DECODE_ONE(match,result) \ case match: \ src += 2; \ *(dest++) = result; \ break; DECODE_ONE ('\'', '\''); DECODE_ONE ('\"', '\"'); DECODE_ONE ('\?', '\?'); DECODE_ONE ('\\', '\\'); DECODE_ONE ('a', '\a'); DECODE_ONE ('b', '\b'); DECODE_ONE ('f', '\f'); DECODE_ONE ('n', '\n'); DECODE_ONE ('r', '\r'); DECODE_ONE ('t', '\t'); DECODE_ONE ('v', '\v'); case 'x': { int val = xtoi_2 (&src[2]); if (val == -1) { /* Should not happen. */ *(dest++) = *(src++); *(dest++) = *(src++); if (*src) *(dest++) = *(src++); if (*src) *(dest++) = *(src++); } else { if (!val) { /* A binary zero is not representable in a C string. */ *(dest++) = '\\'; *(dest++) = '0'; } else *((unsigned char *) dest++) = val; src += 4; } } break; default: { /* Should not happen. */ *(dest++) = *(src++); *(dest++) = *(src++); } } } *(dest++) = 0; return 0; } /* Helper for keylist_cb. This fucntion is invoked for each complete * line assembled by keylist_cb. */ static gpg_error_t keylist_cb_line (struct keylist_ctx *ctx) { char *line; enum { RT_NONE, RT_CRT, RT_CRS, RT_FPR, RT_GRP, RT_UID } rectype = RT_NONE; #define NR_FIELDS 16 char *field[NR_FIELDS]; int fields = 0; struct cert *cert; /* Strip a trailing carriage return. */ if (ctx->pending_len > 0 && ctx->pending[ctx->pending_len - 1] == '\r') ctx->pending_len--; - ctx->pending[ctx->pending_len - 1] = '\0'; + ctx->pending[ctx->pending_len] = '\0'; ctx->pending_len = 0; cert = &ctx->cert; line = ctx->pending; while (line && fields < NR_FIELDS) { field[fields++] = line; line = strchr (line, ':'); if (line) *(line++) = '\0'; } if (!strcmp (field[0], "crt")) rectype = RT_CRT; else if (!strcmp (field[0], "crs")) rectype = RT_CRS; else if (!strcmp (field[0], "fpr")) rectype = RT_FPR; else if (!strcmp (field[0], "grp")) rectype = RT_GRP; else if (!strcmp (field[0], "uid")) rectype = RT_UID; else rectype = RT_NONE; switch (rectype) { case RT_CRT: case RT_CRS: /* Reinitialize CERT. */ if (cert->valid) { gpg_error_t err; /* Return the cert. */ err = export_cert (ctx->cert.fpr, &ctx->cert); if (!err) err = ctx->search_cb (ctx->search_cb_hook, &ctx->cert); if (err) return err; cert_reset (cert); } cert->valid = true; #if 0 /* Field 2 has the trust info. */ if (fields >= 2) set_mainkey_trust_info (key, field[1]); #endif /* Field 3 has the key length. */ if (fields >= 3) { int i = atoi (field[2]); /* Ignore invalid values. */ if (i > 1) cert->length = i; } /* Field 4 has the public key algorithm. */ if (fields >= 4) { int i = atoi (field[3]); if (i >= 1 && i < 128) cert->pubkey_algo = i; } /* Field 5 has the long keyid. Allow short key IDs for the output of an external keyserver listing. */ if (fields >= 5 && strlen (field[4]) <= sizeof (cert->keyid) - 1) strcpy (cert->keyid, field[4]); /* Field 6 has the timestamp (seconds). */ if (fields >= 6) cert->timestamp = parse_timestamp (field[5], NULL); /* Field 7 has the expiration time (seconds). */ if (fields >= 7) cert->expires = parse_timestamp (field[6], NULL); /* Field 8 has the X.509 serial number. */ if (fields >= 8) { cert->issuer_serial = strdup (field[7]); if (!cert->issuer_serial) return gpg_error_from_syserror (); } #if 0 /* Field 9 has the ownertrust. */ if (fields >= 9) set_ownertrust (key, field[8]); #endif /* Field 10 is the issuer name. */ if (fields >= 10) if (decode_c_string (field[9], &cert->issuer_name, 0)) return gpg_error (GPG_ERR_ENOMEM); /* FIXME */ /* Field 11 has the signature class. */ #if 0 /* Field 12 has the capabilities. */ if (fields >= 12) set_mainkey_capability (key, field[11]); #endif break; case RT_UID: if (cert->valid) { /* Field 2 has the trust info, and field 10 has the user ID. Note that more than one UID field can appear. We only remember the last one. It's not used anyway. */ if (fields >= 10 && !cert->uid) { if (decode_c_string (field[9], &cert->uid, 0)) return gpg_error (GPG_ERR_ENOMEM); /* FIXME */ } } break; case RT_FPR: if (cert->valid) { /* Field 10 has the fingerprint (take only the first one). */ if (fields >= 10 && strlen (field[9]) <= sizeof (cert->fpr) - 1) strcpy (cert->fpr, field[9]); /* Field 13 has the gpgsm chain ID (take only the first one). */ if (fields >= 13 && strlen (field[12]) <= sizeof (cert->chain_id) - 1) strcpy (cert->chain_id, field[12]); } break; case RT_GRP: if (cert->valid) { /* Field 10 has the key grip. */ } break; case RT_NONE: /* Unknown record. */ break; } return 0; } /* This is the data line callback handler provided to assuan_transact * in scute_gpgsm_search_certs_by_{grip,fpr}. It buffers incomplete * lines, and is also used to handle the EOF signal directly outside * of assuan_transact. */ static gpg_error_t keylist_cb (void *hook, const void *line_data, size_t line_len) { struct keylist_ctx *ctx = hook; const char *line = line_data; gpg_error_t err; if (!line) { /* This indicates an EOF. */ /* Check for a pending line, in case GPGSM didn't close with a newline. */ if (ctx->pending_len) { err = keylist_cb_line (ctx); if (err) return err; } /* Check for a pending certificate and return it. */ if (ctx->cert.valid) { err = export_cert (ctx->cert.fpr, &ctx->cert); if (!err) err = ctx->search_cb (ctx->search_cb_hook, &ctx->cert); } else err = 0; return err; } while (line_len) { if (*line == '\n') { err = keylist_cb_line (ctx); if (err) return err; } else { if (ctx->pending_len >= MAX_LINE_LEN) return gpg_error (GPG_ERR_LINE_TOO_LONG); ctx->pending[ctx->pending_len++] = *line; } line++; line_len--; } return 0; } struct export_hook { /* The exported data. */ char *buffer; /* The length of the exported data buffer. */ unsigned int buffer_len; /* The size of the allocated exported data buffer. */ unsigned int buffer_size; }; #define EXP_DATA_START 4096 static gpg_error_t export_cert_cb (void *hook, const void *line_data, size_t line_len) { struct export_hook *exp = hook; const char *line = line_data; if (exp->buffer_size - exp->buffer_len < line_len) { unsigned int new_buffer_size = exp->buffer_size ? (exp->buffer_size * 2) : EXP_DATA_START; char *new_buffer = realloc (exp->buffer, new_buffer_size); if (!new_buffer) return gpg_error_from_syserror (); exp->buffer = new_buffer; exp->buffer_size = new_buffer_size; } memcpy (exp->buffer + exp->buffer_len, line, line_len); exp->buffer_len += line_len; return 0; } /* Export the certifciate using a second assuan connection. This is * called during the key listing after a "crt" record has been * received. */ static gpg_error_t export_cert (const char *fpr, struct cert *cert) { gpg_error_t err; assuan_context_t ctx; const char *argv[] = { "gpgsm", "--server", NULL }; #define COMMANDLINELEN 80 char cmd[COMMANDLINELEN]; struct export_hook exp; err = assuan_new (&ctx); if (err) { DEBUG (DBG_CRIT, "failed to allocate assuan context: %s", gpg_strerror (err)); return err; } err = assuan_pipe_connect (ctx, get_gpgsm_path (), argv, NULL, NULL, NULL, 128); if (err) { assuan_release (ctx); DEBUG (DBG_CRIT, "spawning %s\n", get_gpgsm_path ()); return err; } exp.buffer = NULL; exp.buffer_len = 0; exp.buffer_size = 0; snprintf (cmd, sizeof (cmd), "EXPORT --data -- %s", cert->fpr); err = assuan_transact (ctx, cmd, export_cert_cb, &exp, NULL, NULL, NULL, NULL); assuan_release (ctx); if (!err) { cert->cert_der = exp.buffer; cert->cert_der_len = exp.buffer_len; } if (!err) err = scute_agent_is_trusted (fpr, &cert->is_trusted); return err; } /* Search for certificates using a key listing using PATTERN which is * described by MODE. Invoke SEARCH_CB for each certificate found. */ gpg_error_t scute_gpgsm_search_certs (enum keylist_modes mode, const char *pattern, cert_search_cb_t search_cb, void *search_cb_hook) { gpg_error_t err; assuan_context_t ctx; const char *argv[] = { "gpgsm", "--server", NULL }; char line[ASSUAN_LINELENGTH]; struct keylist_ctx keylist_ctx; err = assuan_new (&ctx); if (err) { DEBUG (DBG_CRIT, "failed to allocate assuan context: %s", gpg_strerror (err)); return err; } err = assuan_pipe_connect (ctx, get_gpgsm_path (), argv, NULL, NULL, NULL, 128); if (err) { assuan_release (ctx); DEBUG (DBG_CRIT, "failed to spawn %s\n", get_gpgsm_path ()); return err; } memset (&keylist_ctx, 0, sizeof keylist_ctx); keylist_ctx.search_cb = search_cb; keylist_ctx.search_cb_hook = search_cb_hook; err = assuan_transact (ctx, "OPTION with-key-data", NULL, NULL, NULL, NULL, NULL, NULL); if (err) goto leave; snprintf (line, sizeof line, "LISTKEYS %s%s", mode == KEYLIST_BY_GRIP? "&":"", pattern); err = assuan_transact (ctx, line, keylist_cb, &keylist_ctx, NULL, NULL, NULL, NULL); if (err) goto leave; /* Signal the EOF. This is not done by Assuan for us. */ err = keylist_cb (&keylist_ctx, NULL, 0); if (err) goto leave; leave: cert_reset (&keylist_ctx.cert); assuan_release (ctx); return err; }