/* command.c - SCdaemon command handler * Copyright (C) 2001, 2002, 2003 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 */ #include #include #include #include #include #include #include #include #include #include "scdaemon.h" /* maximum length aloowed as a PIN; used for INQUIRE NEEDPIN */ #define MAXLEN_PIN 100 #define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t)) /* Data used to associate an Assuan context with local server data */ struct server_local_s { ASSUAN_CONTEXT assuan_ctx; }; /* Check whether the option NAME appears in LINE */ static int has_option (const char *line, const char *name) { const char *s; int n = strlen (name); s = strstr (line, name); return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); } /* Note, that this reset_notify is also used for cleanup purposes. */ static void reset_notify (ASSUAN_CONTEXT ctx) { CTRL ctrl = assuan_get_pointer (ctx); if (ctrl->card_ctx) { card_close (ctrl->card_ctx); ctrl->card_ctx = NULL; xfree (ctrl->in_data.value); ctrl->in_data.value = NULL; } } static int option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) { return 0; } /* If the card has not yet been opened, do it. Note that this function returns an Assuan error, so don't map the error a second time */ static AssuanError open_card (CTRL ctrl) { if (!ctrl->card_ctx) { int rc = card_open (&ctrl->card_ctx); if (rc) return map_to_assuan_status (rc); } return 0; } /* SERIALNO Return the serial number of the card using a status reponse. This functon should be used to check for the presence of a card. This function is special in that it can be used to reset the card. Most other functions will return an error when a card change has been detected and the use of this function is therefore required. Background: We want to keep the client clear of handling card changes between operations; i.e. the client can assume that all operations are doneon the same card unless he call this function. */ static int cmd_serialno (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc = 0; char *serial_and_stamp; char *serial; time_t stamp; if ((rc = open_card (ctrl))) return rc; rc = card_get_serial_and_stamp (ctrl->card_ctx, &serial, &stamp); if (rc) return map_to_assuan_status (rc); rc = asprintf (&serial_and_stamp, "%s %lu", serial, (unsigned long)stamp); xfree (serial); if (rc < 0) return ASSUAN_Out_Of_Core; rc = 0; assuan_write_status (ctx, "SERIALNO", serial_and_stamp); free (serial_and_stamp); return 0; } /* LEARN [--force] Learn all useful information of the currently inserted card. When used without the force options, the command might do an INQUIRE like this: INQUIRE KNOWNCARDP The client should just send an "END" if the processing should go on or a "CANCEL" to force the function to terminate with a Cancel error message. The response of this command is a list of status lines formatted as this: S KEYPAIRINFO If there is no certificate yet stored on the card a single "X" is returned as the keygrip. In addition to the keypair info, information about all certificates stored on the card is also returned: S CERTINFO Where CERTINFO is a number indicating the type of certificate: 0 := Unknown 100 := Regular X.509 cert 101 := Trusted X.509 cert 102 := Useful X.509 cert */ static int cmd_learn (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc = 0; int idx; if ((rc = open_card (ctrl))) return rc; /* Unless the force option is used we try a shortcut by identifying the card using a serial number and inquiring the client with that. The client may choose to cancel the operation if he already knows about this card */ { char *serial_and_stamp; char *serial; time_t stamp; rc = card_get_serial_and_stamp (ctrl->card_ctx, &serial, &stamp); if (rc) return map_to_assuan_status (rc); rc = asprintf (&serial_and_stamp, "%s %lu", serial, (unsigned long)stamp); xfree (serial); if (rc < 0) return ASSUAN_Out_Of_Core; rc = 0; assuan_write_status (ctx, "SERIALNO", serial_and_stamp); if (!has_option (line, "--force")) { char *command; rc = asprintf (&command, "KNOWNCARDP %s", serial_and_stamp); if (rc < 0) { free (serial_and_stamp); return ASSUAN_Out_Of_Core; } rc = 0; rc = assuan_inquire (ctx, command, NULL, NULL, 0); free (command); /* (must use standard free here) */ if (rc) { if (rc != ASSUAN_Canceled) log_error ("inquire KNOWNCARDP failed: %s\n", assuan_strerror (rc)); free (serial_and_stamp); return rc; } /* not canceled, so we have to proceeed */ } free (serial_and_stamp); } /* Return information about the certificates. */ for (idx=0; !rc; idx++) { char *certid; int certtype; rc = card_enum_certs (ctrl->card_ctx, idx, &certid, &certtype); if (!rc) { char *buf; buf = xtrymalloc (40 + 1 + strlen (certid) + 1); if (!buf) rc = out_of_core (); else { sprintf (buf, "%d %s", certtype, certid); assuan_write_status (ctx, "CERTINFO", buf); xfree (buf); } } xfree (certid); } if (rc == -1) rc = 0; /* Return information about the keys. */ for (idx=0; !rc; idx++) { unsigned char keygrip[20]; char *keyid; int no_cert = 0; rc = card_enum_keypairs (ctrl->card_ctx, idx, keygrip, &keyid); if (gpg_err_code (rc) == GPG_ERR_MISSING_CERT && keyid) { /* this does happen with an incomplete personalized card; i.e. during the time we have stored the key on the card but not stored the certificate; probably becuase it has not yet been received back from the CA. Note that we must release KEYID in this case. */ rc = 0; no_cert = 1; } if (!rc) { char *buf, *p; buf = p = xtrymalloc (40 + 1 + strlen (keyid) + 1); if (!buf) rc = out_of_core (); else { int i; if (no_cert) *p++ = 'X'; else { for (i=0; i < 20; i++, p += 2) sprintf (p, "%02X", keygrip[i]); } *p++ = ' '; strcpy (p, keyid); assuan_write_status (ctx, "KEYPAIRINFO", buf); xfree (buf); } } xfree (keyid); } if (rc == -1) rc = 0; return map_to_assuan_status (rc); } /* READCERT */ static int cmd_readcert (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc; unsigned char *cert; size_t ncert; if ((rc = open_card (ctrl))) return rc; rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert); if (rc) { log_error ("card_read_cert failed: %s\n", gpg_strerror (rc)); } if (!rc) { rc = assuan_send_data (ctx, cert, ncert); xfree (cert); if (rc) return rc; } return map_to_assuan_status (rc); } /* READKEY Return the public key for the given cert or key ID as an standard S-Expression. */ static int cmd_readkey (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc; unsigned char *cert = NULL; size_t ncert, n; KsbaCert kc = NULL; KsbaSexp p; if ((rc = open_card (ctrl))) return rc; rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert); if (rc) { log_error ("card_read_cert failed: %s\n", gpg_strerror (rc)); goto leave; } kc = ksba_cert_new (); if (!kc) { rc = out_of_core (); xfree (cert); goto leave; } rc = ksba_cert_init_from_mem (kc, cert, ncert); if (rc) { log_error ("failed to parse the certificate: %s\n", ksba_strerror (rc)); rc = map_ksba_err (rc); goto leave; } p = ksba_cert_get_public_key (kc); if (!p) { rc = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } n = gcry_sexp_canon_len (p, 0, NULL, NULL); rc = assuan_send_data (ctx, p, n); rc = map_assuan_err (rc); xfree (p); leave: ksba_cert_release (kc); xfree (cert); return map_to_assuan_status (rc); } /* SETDATA The client should use this command to tell us the data he want to sign. */ static int cmd_setdata (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int n; char *p; unsigned char *buf; /* parse the hexstring */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (*p) return set_error (Parameter_Error, "invalid hexstring"); if ((n&1)) return set_error (Parameter_Error, "odd number of digits"); n /= 2; buf = xtrymalloc (n); if (!buf) return ASSUAN_Out_Of_Core; ctrl->in_data.value = buf; ctrl->in_data.valuelen = n; for (p=line, n=0; n < ctrl->in_data.valuelen; p += 2, n++) buf[n] = xtoi_2 (p); return 0; } static int pin_cb (void *opaque, const char *info, char **retstr) { ASSUAN_CONTEXT ctx = opaque; char *command; int rc; char *value; size_t valuelen; *retstr = NULL; log_debug ("asking for PIN '%s'\n", info); rc = asprintf (&command, "NEEDPIN %s", info); if (rc < 0) return out_of_core (); /* FIXME: Write an inquire function which returns the result in secure memory */ rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); free (command); if (rc) return map_assuan_err (rc); if (!valuelen || value[valuelen-1]) { /* We require that the returned value is an UTF-8 string */ xfree (value); return gpg_error (GPG_ERR_INV_RESPONSE); } *retstr = value; return 0; } /* PKSIGN */ static int cmd_pksign (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc; void *outdata; size_t outdatalen; char *keyidstr; if ((rc = open_card (ctrl))) return rc; /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid */ keyidstr = strdup (line); if (!keyidstr) return ASSUAN_Out_Of_Core; rc = card_sign (ctrl->card_ctx, keyidstr, GCRY_MD_SHA1, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); free (keyidstr); if (rc) { log_error ("card_sign failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return map_to_assuan_status (rc); } /* PKDECRYPT */ static int cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc; void *outdata; size_t outdatalen; char *keyidstr; if ((rc = open_card (ctrl))) return rc; keyidstr = strdup (line); if (!keyidstr) return ASSUAN_Out_Of_Core; rc = card_decipher (ctrl->card_ctx, keyidstr, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); free (keyidstr); if (rc) { log_error ("card_create_signature failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return map_to_assuan_status (rc); } /* Tell the assuan library about our commands */ static int register_commands (ASSUAN_CONTEXT ctx) { static struct { const char *name; int (*handler)(ASSUAN_CONTEXT, char *line); } table[] = { { "SERIALNO", cmd_serialno }, { "LEARN", cmd_learn }, { "READCERT", cmd_readcert }, { "READKEY", cmd_readkey }, { "SETDATA", cmd_setdata }, { "PKSIGN", cmd_pksign }, { "PKDECRYPT", cmd_pkdecrypt }, { "INPUT", NULL }, { "OUTPUT", NULL }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler); if (rc) return rc; } assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready"); assuan_register_reset_notify (ctx, reset_notify); assuan_register_option_handler (ctx, option_handler); return 0; } /* Startup the server. If LISTEN_FD is given as -1, this is simple piper server, otherwise it is a regular server */ void scd_command_handler (int listen_fd) { int rc; ASSUAN_CONTEXT ctx; struct server_control_s ctrl; memset (&ctrl, 0, sizeof ctrl); scd_init_default_ctrl (&ctrl); if (listen_fd == -1) { int filedes[2]; filedes[0] = 0; filedes[1] = 1; rc = assuan_init_pipe_server (&ctx, filedes); } else { rc = assuan_init_socket_server (&ctx, listen_fd); } if (rc) { log_error ("failed to initialize the server: %s\n", assuan_strerror(rc)); scd_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to register commands with Assuan: %s\n", assuan_strerror(rc)); scd_exit (2); } assuan_set_pointer (ctx, &ctrl); ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); ctrl.server_local->assuan_ctx = ctx; if (DBG_ASSUAN) assuan_set_log_stream (ctx, log_get_stream ()); for (;;) { rc = assuan_accept (ctx); if (rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", assuan_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", assuan_strerror (rc)); continue; } } reset_notify (ctx); /* used for cleanup */ assuan_deinit_server (ctx); }