/* call-scd.c - fork of the scdaemon to do SC operations * Copyright (C) 2001, 2002, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * GnuPG 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ /* Fixme: For now we have serialized all access to the scdaemon which make sense becuase the scdaemon can't handle concurrent connections right now. We should however keep a list of connections and lock just that connection - it migth make sense to implemtn parts of this in Assuan.*/ #include #include #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #endif #ifdef USE_GNU_PTH # include #endif #include "agent.h" #include #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif static ASSUAN_CONTEXT scd_ctx = NULL; #ifdef USE_GNU_PTH static pth_mutex_t scd_lock; #endif /* We need to keep track of the connection currently using the SCD. For a pipe server this is all a NOP because the connection will always have the connection indicator -1. agent_reset_scd releases the active connection; i.e. sets it back to -1, so that a new connection can start using the SCD. If we eventually allow multiple SCD session we will either make scdaemon multi-threaded or fork of a new scdaemon and let it see how it can get access to a reader. */ static int active_connection_fd = -1; static int active_connection = 0; /* Callback parameter for learn card */ struct learn_parm_s { void (*kpinfo_cb)(void*, const char *); void *kpinfo_cb_arg; void (*certinfo_cb)(void*, const char *); void *certinfo_cb_arg; void (*sinfo_cb)(void*, const char *, size_t, const char *); void *sinfo_cb_arg; }; struct inq_needpin_s { ASSUAN_CONTEXT ctx; int (*getpin_cb)(void *, const char *, char*, size_t); void *getpin_cb_arg; }; /* This function must be called once to initialize this module. This has to be done before a second thread is spawned. We can't do the static initialization because Pth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ void initialize_module_call_scd (void) { #ifdef USE_GNU_PTH static int initialized; if (!initialized) if (pth_mutex_init (&scd_lock)) initialized = 1; #endif /*USE_GNU_PTH*/ } static int unlock_scd (int rc) { #ifdef USE_GNU_PTH if (!pth_mutex_release (&scd_lock)) { log_error ("failed to release the SCD lock\n"); if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } #endif /*USE_GNU_PTH*/ return rc; } /* To make sure we leave no secrets in our image after forking of the scdaemon, we use this callback. */ static void atfork_cb (void *opaque, int where) { if (!where) gcry_control (GCRYCTL_TERM_SECMEM); } /* Fork off the SCdaemon if this has not already been done. Note that this fucntion alos locks the daemon. */ static int start_scd (ctrl_t ctrl) { int rc; const char *pgmname; ASSUAN_CONTEXT ctx; const char *argv[3]; int no_close_list[3]; int i; #ifdef USE_GNU_PTH if (!pth_mutex_acquire (&scd_lock, 0, NULL)) { log_error ("failed to acquire the SCD lock\n"); return gpg_error (GPG_ERR_INTERNAL); } #endif if (scd_ctx) { pid_t pid; /* If we are not the connection currently using the SCD, return an error. */ if (!active_connection) { active_connection_fd = ctrl->connection_fd; active_connection = 1; } else if (ctrl->connection_fd != active_connection_fd) return unlock_scd (gpg_error (GPG_ERR_CONFLICT)); /* Okay, we already started the scdaemon and it is used by us.*/ /* We better do a sanity check now to see whether it has accidently died. */ #ifndef HAVE_W32_SYSTEM pid = assuan_get_pid (scd_ctx); if (pid != (pid_t)(-1) && pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) { assuan_disconnect (scd_ctx); scd_ctx = NULL; } else #endif return 0; } if (opt.verbose) log_info ("no running SCdaemon - starting it\n"); if (fflush (NULL)) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); log_error ("error flushing pending output: %s\n", strerror (errno)); return unlock_scd (tmperr); } if (!opt.scdaemon_program || !*opt.scdaemon_program) opt.scdaemon_program = GNUPG_DEFAULT_SCDAEMON; if ( !(pgmname = strrchr (opt.scdaemon_program, '/'))) pgmname = opt.scdaemon_program; else pgmname++; argv[0] = pgmname; argv[1] = "--server"; argv[2] = NULL; i=0; if (!opt.running_detached) { if (log_get_fd () != -1) no_close_list[i++] = log_get_fd (); no_close_list[i++] = fileno (stderr); } no_close_list[i] = -1; /* Connect to the pinentry and perform initial handshaking */ rc = assuan_pipe_connect2 (&ctx, opt.scdaemon_program, (char**)argv, no_close_list, atfork_cb, NULL); if (rc) { log_error ("can't connect to the SCdaemon: %s\n", assuan_strerror (rc)); return unlock_scd (gpg_error (GPG_ERR_NO_SCDAEMON)); } scd_ctx = ctx; active_connection_fd = ctrl->connection_fd; active_connection = 1; if (DBG_ASSUAN) log_debug ("connection to SCdaemon established\n"); /* Tell the scdaemon that we want him to send us an event signal. But only do this if we are running as a regular sever and not simply as a pipe server. */ /* Fixme: gpg-agent does not use this signal yet. */ /* if (ctrl->connection_fd != -1) */ /* { */ /* #ifndef HAVE_W32_SYSTEM */ /* char buf[100]; */ /* sprintf (buf, "OPTION event-signal=%d", SIGUSR2); */ /* assuan_transact (scd_ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); */ /* #endif */ /* } */ return 0; } /* Reset the SCD if it has been used. */ int agent_reset_scd (ctrl_t ctrl) { int rc = 0; #ifdef USE_GNU_PTH if (!pth_mutex_acquire (&scd_lock, 0, NULL)) { log_error ("failed to acquire the SCD lock for reset\n"); return gpg_error (GPG_ERR_INTERNAL); } #endif if (active_connection && active_connection_fd == ctrl->connection_fd) { if (scd_ctx) rc = assuan_transact (scd_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); active_connection_fd = -1; active_connection = 0; } return unlock_scd (map_assuan_err (rc)); } /* Return a new malloced string by unescaping the string S. Escaping is percent escaping and '+'/space mapping. A binary Nul will silently be replaced by a 0xFF. Function returns NULL to indicate an out of memory status. */ static char * unescape_status_string (const unsigned char *s) { char *buffer, *d; buffer = d = xtrymalloc (strlen (s)+1); if (!buffer) return NULL; while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d = xtoi_2 (s); if (!*d) *d = '\xff'; d++; s += 2; } else if (*s == '+') { *d++ = ' '; s++; } else *d++ = *s++; } *d = 0; return buffer; } static AssuanError learn_status_cb (void *opaque, const char *line) { struct learn_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "CERTINFO", keywordlen)) { parm->certinfo_cb (parm->certinfo_cb_arg, line); } else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { parm->kpinfo_cb (parm->kpinfo_cb_arg, line); } else if (keywordlen && *line) { parm->sinfo_cb (parm->sinfo_cb_arg, keyword, keywordlen, line); } return 0; } /* Perform the LEARN command and return a list of all private keys stored on the card. */ int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), void *certinfo_cb_arg, void (*sinfo_cb)(void*, const char *, size_t, const char *), void *sinfo_cb_arg) { int rc; struct learn_parm_s parm; rc = start_scd (ctrl); if (rc) return rc; memset (&parm, 0, sizeof parm); parm.kpinfo_cb = kpinfo_cb; parm.kpinfo_cb_arg = kpinfo_cb_arg; parm.certinfo_cb = certinfo_cb; parm.certinfo_cb_arg = certinfo_cb_arg; parm.sinfo_cb = sinfo_cb; parm.sinfo_cb_arg = sinfo_cb_arg; rc = assuan_transact (scd_ctx, "LEARN --force", NULL, NULL, NULL, NULL, learn_status_cb, &parm); if (rc) return unlock_scd (map_assuan_err (rc)); return unlock_scd (0); } static AssuanError get_serialno_cb (void *opaque, const char *line) { char **serialno = opaque; const char *keyword = line; const char *s; int keywordlen, n; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { if (*serialno) return ASSUAN_Unexpected_Status; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1)|| !(spacep (s) || !*s) ) return ASSUAN_Invalid_Status; *serialno = xtrymalloc (n+1); if (!*serialno) return ASSUAN_Out_Of_Core; memcpy (*serialno, line, n); (*serialno)[n] = 0; } return 0; } /* Return the serial number of the card or an appropriate error. The serial number is returned as a hexstring. */ int agent_card_serialno (ctrl_t ctrl, char **r_serialno) { int rc; char *serialno = NULL; rc = start_scd (ctrl); if (rc) return rc; rc = assuan_transact (scd_ctx, "SERIALNO", NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (rc) { xfree (serialno); return unlock_scd (map_assuan_err (rc)); } *r_serialno = serialno; return unlock_scd (0); } static AssuanError membuf_data_cb (void *opaque, const void *buffer, size_t length) { membuf_t *data = opaque; if (buffer) put_membuf (data, buffer, length); return 0; } /* Handle the NEEDPIN inquiry. */ static AssuanError inq_needpin (void *opaque, const char *line) { struct inq_needpin_s *parm = opaque; char *pin; size_t pinlen; int rc; if (!(!strncmp (line, "NEEDPIN", 7) && (line[7] == ' ' || !line[7]))) { log_error ("unsupported inquiry `%s'\n", line); return ASSUAN_Inquire_Unknown; } line += 7; pinlen = 90; pin = gcry_malloc_secure (pinlen); if (!pin) return ASSUAN_Out_Of_Core; rc = parm->getpin_cb (parm->getpin_cb_arg, line, pin, pinlen); if (rc) rc = ASSUAN_Canceled; if (!rc) rc = assuan_send_data (parm->ctx, pin, pinlen); xfree (pin); return rc; } /* Create a signature using the current card */ int agent_card_pksign (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; struct inq_needpin_s inqparm; size_t len; unsigned char *sigbuf; size_t sigbuflen; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; if (indatalen*2 + 50 > DIM(line)) return unlock_scd (gpg_error (GPG_ERR_GENERAL)); sprintf (line, "SETDATA "); p = line + strlen (line); for (i=0; i < indatalen ; i++, p += 2 ) sprintf (p, "%02X", indata[i]); rc = assuan_transact (scd_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (map_assuan_err (rc)); init_membuf (&data, 1024); inqparm.ctx = scd_ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; snprintf (line, DIM(line)-1, ctrl->use_auth_call? "PKAUTH %s":"PKSIGN %s", keyid); line[DIM(line)-1] = 0; rc = assuan_transact (scd_ctx, line, membuf_data_cb, &data, inq_needpin, &inqparm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (map_assuan_err (rc)); } sigbuf = get_membuf (&data, &sigbuflen); /* Create an S-expression from it which is formatted like this: "(7:sig-val(3:rsa(1:sSIGBUFLEN:SIGBUF)))" */ *r_buflen = 21 + 11 + sigbuflen + 4; *r_buf = xtrymalloc (*r_buflen); if (!*r_buf) { gpg_error_t tmperr = out_of_core (); xfree (*r_buf); return unlock_scd (tmperr); } p = stpcpy (*r_buf, "(7:sig-val(3:rsa(1:s" ); sprintf (p, "%u:", (unsigned int)sigbuflen); p += strlen (p); memcpy (p, sigbuf, sigbuflen); p += sigbuflen; strcpy (p, ")))"); xfree (sigbuf); assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)); return unlock_scd (0); } /* Decipher INDATA using the current card. Note that the returned value is */ int agent_card_pkdecrypt (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; struct inq_needpin_s inqparm; size_t len; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; /* FIXME: use secure memory where appropriate */ if (indatalen*2 + 50 > DIM(line)) return unlock_scd (gpg_error (GPG_ERR_GENERAL)); sprintf (line, "SETDATA "); p = line + strlen (line); for (i=0; i < indatalen ; i++, p += 2 ) sprintf (p, "%02X", indata[i]); rc = assuan_transact (scd_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (map_assuan_err (rc)); init_membuf (&data, 1024); inqparm.ctx = scd_ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; snprintf (line, DIM(line)-1, "PKDECRYPT %s", keyid); line[DIM(line)-1] = 0; rc = assuan_transact (scd_ctx, line, membuf_data_cb, &data, inq_needpin, &inqparm, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (map_assuan_err (rc)); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return unlock_scd (gpg_error (GPG_ERR_ENOMEM)); return unlock_scd (0); } /* Read a certificate with ID into R_BUF and R_BUFLEN. */ int agent_card_readcert (ctrl_t ctrl, const char *id, char **r_buf, size_t *r_buflen) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; init_membuf (&data, 1024); snprintf (line, DIM(line)-1, "READCERT %s", id); line[DIM(line)-1] = 0; rc = assuan_transact (scd_ctx, line, membuf_data_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (map_assuan_err (rc)); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return unlock_scd (gpg_error (GPG_ERR_ENOMEM)); return unlock_scd (0); } /* Read a key with ID and return it in an allocate buffer pointed to by r_BUF as a valid S-expression. */ int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len, buflen; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; init_membuf (&data, 1024); snprintf (line, DIM(line)-1, "READKEY %s", id); line[DIM(line)-1] = 0; rc = assuan_transact (scd_ctx, line, membuf_data_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (map_assuan_err (rc)); } *r_buf = get_membuf (&data, &buflen); if (!*r_buf) return unlock_scd (gpg_error (GPG_ERR_ENOMEM)); if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL)) { xfree (*r_buf); *r_buf = NULL; return unlock_scd (gpg_error (GPG_ERR_INV_VALUE)); } return unlock_scd (0); } /* Type used with the card_getattr_cb. */ struct card_getattr_parm_s { const char *keyword; /* Keyword to look for. */ size_t keywordlen; /* strlen of KEYWORD. */ char *data; /* Malloced and unescaped data. */ int error; /* ERRNO value or 0 on success. */ }; /* Callback function for agent_card_getattr. */ static assuan_error_t card_getattr_cb (void *opaque, const char *line) { struct card_getattr_parm_s *parm = opaque; const char *keyword = line; int keywordlen; if (parm->data) return 0; /* We want only the first occurrence. */ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == parm->keywordlen && !memcmp (keyword, parm->keyword, keywordlen)) { parm->data = unescape_status_string (line); if (!parm->data) parm->error = errno; } return 0; } /* Call the agent to retrieve a single line data object. On success the object is malloced and stored at RESULT; it is guaranteed that NULL is never stored in this case. On error an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result) { int err; struct card_getattr_parm_s parm; char line[ASSUAN_LINELENGTH]; *result = NULL; if (!*name) return gpg_error (GPG_ERR_INV_VALUE); memset (&parm, 0, sizeof parm); parm.keyword = name; parm.keywordlen = strlen (name); /* We assume that NAME does not need escaping. */ if (8 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); stpcpy (stpcpy (line, "GETATTR "), name); err = start_scd (ctrl); if (err) return err; err = map_assuan_err (assuan_transact (scd_ctx, line, NULL, NULL, NULL, NULL, card_getattr_cb, &parm)); if (!err && parm.error) err = gpg_error_from_errno (parm.error); if (!err && !parm.data) err = gpg_error (GPG_ERR_NO_DATA); if (!err) *result = parm.data; else xfree (parm.data); return unlock_scd (err); } static AssuanError pass_status_thru (void *opaque, const char *line) { ASSUAN_CONTEXT ctx = opaque; char keyword[200]; int i; for (i=0; *line && !spacep (line) && i < DIM(keyword)-1; line++, i++) keyword[i] = *line; keyword[i] = 0; /* truncate any remaining keyword stuff. */ for (; *line && !spacep (line); line++) ; while (spacep (line)) line++; assuan_write_status (ctx, keyword, line); return 0; } static AssuanError pass_data_thru (void *opaque, const void *buffer, size_t length) { ASSUAN_CONTEXT ctx = opaque; assuan_send_data (ctx, buffer, length); return 0; } /* Send the line CMDLINE with command for the SCDdaemon to it and send all status messages back. This command is used as a general quoting mechanism to pass everything verbatim to SCDAEMOPN. The PIN inquirey is handled inside gpg-agent. */ int agent_card_scd (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context) { int rc; struct inq_needpin_s inqparm; rc = start_scd (ctrl); if (rc) return rc; inqparm.ctx = scd_ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; rc = assuan_transact (scd_ctx, cmdline, pass_data_thru, assuan_context, inq_needpin, &inqparm, pass_status_thru, assuan_context); if (rc) { return unlock_scd (map_assuan_err (rc)); } return unlock_scd (0); }