diff --git a/common/ssh-utils.c b/common/ssh-utils.c index 013b28e5b..bd7f192ea 100644 --- a/common/ssh-utils.c +++ b/common/ssh-utils.c @@ -1,354 +1,644 @@ /* ssh-utils.c - Secure Shell helper functions * Copyright (C) 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - 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. * * or both in parallel, as here. * * This file 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, see . */ #include #include #include #include #include #include "util.h" #include "ssh-utils.h" +#include "openpgpdefs.h" /* Return true if KEYPARMS holds an EdDSA key. */ static int is_eddsa (gcry_sexp_t keyparms) { int result = 0; gcry_sexp_t list; const char *s; size_t n; int i; list = gcry_sexp_find_token (keyparms, "flags", 0); for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--) { s = gcry_sexp_nth_data (list, i, &n); if (!s) continue; /* Not a data element. */ if (n == 5 && !memcmp (s, "eddsa", 5)) { result = 1; break; } } gcry_sexp_release (list); return result; } /* Dummy functions for es_mopen. */ static void *dummy_realloc (void *mem, size_t size) { (void) size; return mem; } static void dummy_free (void *mem) { (void) mem; } /* Return the Secure Shell type fingerprint for KEY using digest ALGO. The length of the fingerprint is returned at R_LEN and the fingerprint itself at R_FPR. In case of a error code is returned and NULL stored at R_FPR. */ static gpg_error_t get_fingerprint (gcry_sexp_t key, int algo, void **r_fpr, size_t *r_len, int as_string) { gpg_error_t err; gcry_sexp_t list = NULL; gcry_sexp_t l2 = NULL; const char *s; char *name = NULL; int idx; const char *elems; gcry_md_hd_t md = NULL; int blobmode = 0; *r_fpr = NULL; *r_len = 0; /* Check that the first element is valid. */ list = gcry_sexp_find_token (key, "public-key", 0); if (!list) list = gcry_sexp_find_token (key, "private-key", 0); if (!list) list = gcry_sexp_find_token (key, "protected-private-key", 0); if (!list) list = gcry_sexp_find_token (key, "shadowed-private-key", 0); if (!list) { err = gpg_err_make (default_errsource, GPG_ERR_UNKNOWN_SEXP); goto leave; } l2 = gcry_sexp_cadr (list); gcry_sexp_release (list); list = l2; l2 = NULL; name = gcry_sexp_nth_string (list, 0); if (!name) { err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP); goto leave; } err = gcry_md_open (&md, algo, 0); if (err) goto leave; switch (gcry_pk_map_name (name)) { case GCRY_PK_RSA: elems = "en"; gcry_md_write (md, "\0\0\0\x07ssh-rsa", 11); break; case GCRY_PK_DSA: elems = "pqgy"; gcry_md_write (md, "\0\0\0\x07ssh-dss", 11); break; case GCRY_PK_ECC: if (is_eddsa (list)) { elems = "q"; blobmode = 1; /* For now there is just one curve, thus no need to switch on it. */ gcry_md_write (md, "\0\0\0\x0b" "ssh-ed25519", 15); } else { /* We only support the 3 standard curves for now. It is just a quick hack. */ elems = "q"; gcry_md_write (md, "\0\0\0\x13" "ecdsa-sha2-nistp", 20); l2 = gcry_sexp_find_token (list, "curve", 0); if (!l2) elems = ""; else { gcry_free (name); name = gcry_sexp_nth_string (l2, 1); gcry_sexp_release (l2); l2 = NULL; if (!name) elems = ""; else if (!strcmp (name, "NIST P-256")||!strcmp (name, "nistp256")) gcry_md_write (md, "256\0\0\0\x08nistp256", 15); else if (!strcmp (name, "NIST P-384")||!strcmp (name, "nistp384")) gcry_md_write (md, "384\0\0\0\x08nistp384", 15); else if (!strcmp (name, "NIST P-521")||!strcmp (name, "nistp521")) gcry_md_write (md, "521\0\0\0\x08nistp521", 15); else elems = ""; } if (!*elems) err = gpg_err_make (default_errsource, GPG_ERR_UNKNOWN_CURVE); } break; default: elems = ""; err = gpg_err_make (default_errsource, GPG_ERR_PUBKEY_ALGO); break; } if (err) goto leave; for (idx = 0, s = elems; *s; s++, idx++) { l2 = gcry_sexp_find_token (list, s, 1); if (!l2) { err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP); goto leave; } if (blobmode) { const char *blob; size_t bloblen; unsigned char lenbuf[4]; blob = gcry_sexp_nth_data (l2, 1, &bloblen); if (!blob) { err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP); goto leave; } blob++; bloblen--; lenbuf[0] = bloblen >> 24; lenbuf[1] = bloblen >> 16; lenbuf[2] = bloblen >> 8; lenbuf[3] = bloblen; gcry_md_write (md, lenbuf, 4); gcry_md_write (md, blob, bloblen); } else { gcry_mpi_t a; unsigned char *buf; size_t buflen; a = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG); gcry_sexp_release (l2); l2 = NULL; if (!a) { err = gpg_err_make (default_errsource, GPG_ERR_INV_SEXP); goto leave; } err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a); gcry_mpi_release (a); if (err) goto leave; gcry_md_write (md, buf, buflen); gcry_free (buf); } } if (as_string) { const char *algo_name; char *fpr; /* Prefix string with the algorithm name and a colon. */ algo_name = gcry_md_algo_name (algo); *r_fpr = xtrymalloc (strlen (algo_name) + 1 + 3 * gcry_md_get_algo_dlen (algo) + 1); if (*r_fpr == NULL) { err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); goto leave; } memcpy (*r_fpr, algo_name, strlen (algo_name)); fpr = (char *) *r_fpr + strlen (algo_name); *fpr++ = ':'; if (algo == GCRY_MD_MD5) { bin2hexcolon (gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo), fpr); strlwr (fpr); } else { struct b64state b64s; estream_t stream; char *p; long int len; /* Write the base64-encoded hash to fpr. */ stream = es_mopen (fpr, 3 * gcry_md_get_algo_dlen (algo) + 1, 0, 0, dummy_realloc, dummy_free, "w"); if (stream == NULL) { err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); goto leave; } err = b64enc_start_es (&b64s, stream, ""); if (err) { es_fclose (stream); goto leave; } err = b64enc_write (&b64s, gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo)); if (err) { es_fclose (stream); goto leave; } /* Finish, get the length, and close the stream. */ err = b64enc_finish (&b64s); len = es_ftell (stream); es_fclose (stream); if (err) goto leave; /* Terminate. */ fpr[len] = 0; /* Strip the trailing padding characters. */ for (p = fpr + len - 1; p > fpr && *p == '='; p--) *p = 0; } *r_len = strlen (*r_fpr) + 1; } else { *r_len = gcry_md_get_algo_dlen (algo); *r_fpr = xtrymalloc (*r_len); if (!*r_fpr) { err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); goto leave; } memcpy (*r_fpr, gcry_md_read (md, algo), *r_len); } err = 0; leave: gcry_free (name); gcry_sexp_release (l2); gcry_md_close (md); gcry_sexp_release (list); return err; } /* Return the Secure Shell type fingerprint for KEY using digest ALGO. The length of the fingerprint is returned at R_LEN and the fingerprint itself at R_FPR. In case of an error an error code is returned and NULL stored at R_FPR. */ gpg_error_t ssh_get_fingerprint (gcry_sexp_t key, int algo, void **r_fpr, size_t *r_len) { return get_fingerprint (key, algo, r_fpr, r_len, 0); } /* Return the Secure Shell type fingerprint for KEY using digest ALGO as a string. The fingerprint is mallcoed and stored at R_FPRSTR. In case of an error an error code is returned and NULL stored at R_FPRSTR. */ gpg_error_t ssh_get_fingerprint_string (gcry_sexp_t key, int algo, char **r_fprstr) { gpg_error_t err; size_t dummy; void *string; err = get_fingerprint (key, algo, &string, &dummy, 1); *r_fprstr = string; return err; } + + +/* Write the uint32 contained in UINT32 to STREAM. */ +static gpg_error_t +stream_write_uint32 (estream_t stream, u32 uint32) +{ + unsigned char buffer[4]; + gpg_error_t err; + int ret; + + buffer[0] = uint32 >> 24; + buffer[1] = uint32 >> 16; + buffer[2] = uint32 >> 8; + buffer[3] = uint32 >> 0; + + ret = es_write (stream, buffer, sizeof (buffer), NULL); + if (ret) + err = gpg_error_from_syserror (); + else + err = 0; + + return err; +} + +/* Write SIZE bytes from BUFFER to STREAM. */ +static gpg_error_t +stream_write_data (estream_t stream, const unsigned char *buffer, size_t size) +{ + gpg_error_t err; + int ret; + + ret = es_write (stream, buffer, size, NULL); + if (ret) + err = gpg_error_from_syserror (); + else + err = 0; + + return err; +} + +/* Write a binary string from STRING of size STRING_N to STREAM. */ +static gpg_error_t +stream_write_string (estream_t stream, + const unsigned char *string, u32 string_n) +{ + gpg_error_t err; + + err = stream_write_uint32 (stream, string_n); + if (err) + goto out; + + err = stream_write_data (stream, string, string_n); + + out: + + return err; +} + +/* Write a C-string from STRING to STREAM. */ +static gpg_error_t +stream_write_cstring (estream_t stream, const char *string) +{ + gpg_error_t err; + + err = stream_write_string (stream, + (const unsigned char *) string, strlen (string)); + + return err; +} + +/* Write the MPI contained in MPINT to STREAM. */ +static gpg_error_t +stream_write_mpi (estream_t stream, gcry_mpi_t mpint) +{ + unsigned char *mpi_buffer; + size_t mpi_buffer_n; + gpg_error_t err; + + mpi_buffer = NULL; + + err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint); + if (err) + goto out; + + err = stream_write_string (stream, mpi_buffer, mpi_buffer_n); + + out: + + xfree (mpi_buffer); + + return err; +} + + +/* Encode a key in SEXP, in SSH format. */ +static gpg_error_t +sexp_to_sshblob (gcry_sexp_t sexp, const char *identifier, int is_eddsa_flag, + const char *curve, const char *elems, + void **r_blob, size_t *r_blob_size) +{ + gpg_error_t err = 0; + gcry_sexp_t value_list = NULL; + gcry_sexp_t value_pair = NULL; + estream_t stream = NULL; + const char *p_elems; + const char *data; + size_t datalen; + + *r_blob = NULL; + *r_blob_size = 0; + + stream = es_fopenmem (0, "r+b"); + if (!stream) + { + err = gpg_error_from_syserror (); + goto out; + } + + /* Get key value list. */ + value_list = gcry_sexp_cadr (sexp); + if (!value_list) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + + err = stream_write_cstring (stream, identifier); + if (err) + goto out; + + if (curve && !is_eddsa_flag) + { + /* ECDSA requires the curve name. */ + err = stream_write_cstring (stream, curve); + if (err) + goto out; + } + + /* Write the parameters. */ + for (p_elems = elems; *p_elems; p_elems++) + { + gcry_sexp_release (value_pair); + value_pair = gcry_sexp_find_token (value_list, p_elems, 1); + if (!value_pair) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + if (is_eddsa_flag) + { + data = gcry_sexp_nth_data (value_pair, 1, &datalen); + if (!data) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + if ((datalen & 1) && *data == 0x40) + { /* Remove the prefix 0x40. */ + data++; + datalen--; + } + err = stream_write_string (stream, data, datalen); + if (err) + goto out; + } + else + { + gcry_mpi_t mpi; + + /* Note that we need to use STD format; i.e. prepend a 0x00 + to indicate a positive number if the high bit is set. */ + mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD); + if (!mpi) + { + err = gpg_error (GPG_ERR_INV_SEXP); + goto out; + } + err = stream_write_mpi (stream, mpi); + gcry_mpi_release (mpi); + if (err) + goto out; + } + } + + if (es_fclose_snatch (stream, r_blob, r_blob_size)) + { + err = gpg_error_from_syserror (); + goto out; + } + stream = NULL; + + out: + gcry_sexp_release (value_list); + gcry_sexp_release (value_pair); + es_fclose (stream); + + return err; +} + +/* For KEY in S-expression, write it in SSH base64 format to STREAM, + adding COMMENT. */ +gpg_error_t +ssh_public_key_in_base64 (gcry_sexp_t key, estream_t stream, + const char *comment) +{ + gpg_error_t err = 0; + int algo; + int is_eddsa_flag = 0; + const char *curve = NULL; + const char *pub_elements = NULL; + const char *identifier = NULL; + void *blob = NULL; + size_t bloblen; + struct b64state b64_state; + + algo = get_pk_algo_from_key (key); + if (algo == 0) + return gpg_error (GPG_ERR_PUBKEY_ALGO); + + if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA) + { + curve = gcry_pk_get_curve (key, 0, NULL); + if (!curve) + return gpg_error (GPG_ERR_INV_CURVE); + } + + switch (algo) + { + case PUBKEY_ALGO_RSA: + identifier = "ssh-rsa"; + pub_elements = "en"; + break; + + case PUBKEY_ALGO_ECDSA: + if (!strcmp (curve, "nistp256")) + identifier = "ecdsa-sha2-nistp256"; + else if (!strcmp (curve, "nistp384")) + identifier = "ecdsa-sha2-nistp384"; + else if (!strcmp (curve, "nistp521")) + identifier = "ecdsa-sha2-nistp521"; + else + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + pub_elements = "q"; + break; + + case PUBKEY_ALGO_EDDSA: + is_eddsa_flag = 1; + if (!strcmp (curve, "Ed25519")) + identifier = "ssh-ed25519"; + else if (!strcmp (curve, "Ed448")) + identifier = "ssh-ed448"; + else + err = gpg_error (GPG_ERR_UNKNOWN_CURVE); + pub_elements = "q"; + break; + + default: + err = gpg_error (GPG_ERR_PUBKEY_ALGO); + break; + } + + if (err) + return err; + + err = sexp_to_sshblob (key, identifier, is_eddsa_flag, curve, pub_elements, + &blob, &bloblen); + if (err) + return err; + + es_fprintf (stream, "%s ", identifier); + + err = b64enc_start_es (&b64_state, stream, ""); + if (err) + { + es_free (blob); + return err; + } + + err = b64enc_write (&b64_state, blob, bloblen); + b64enc_finish (&b64_state); + es_free (blob); + if (err) + return err; + + if (comment) + es_fprintf (stream, " %s", comment); + + return err; +} diff --git a/common/ssh-utils.h b/common/ssh-utils.h index 53d9f550c..5b5565c53 100644 --- a/common/ssh-utils.h +++ b/common/ssh-utils.h @@ -1,41 +1,43 @@ /* ssh-utils.c - Secure Shell helper function definitions * Copyright (C) 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of either * * - the GNU Lesser General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at * your option) any later version. * * or * * - 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. * * or both in parallel, as here. * * This file 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, see . */ #ifndef GNUPG_COMMON_SSH_UTILS_H #define GNUPG_COMMON_SSH_UTILS_H gpg_error_t ssh_get_fingerprint (gcry_sexp_t key, int algo, void **r_fpr, size_t *r_len); gpg_error_t ssh_get_fingerprint_string (gcry_sexp_t key, int algo, char **r_fprstr); +gpg_error_t ssh_public_key_in_base64 (gcry_sexp_t key, estream_t stream, + const char *comment); #endif /*GNUPG_COMMON_SSH_UTILS_H*/ diff --git a/scd/command.c b/scd/command.c index 916a141ae..dfd1ee538 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1,2957 +1,2997 @@ /* command.c - SCdaemon command handler * Copyright (C) 2001, 2002, 2003, 2004, 2005, * 2007, 2008, 2009, 2011 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 3 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, see . */ #include #include #include #include #include #include #include #include #ifdef USE_NPTH # include #endif #include "scdaemon.h" #include #include #include "iso7816.h" #include "apdu.h" /* Required for apdu_*_reader (). */ #include "atr.h" #ifdef HAVE_LIBUSB #include "ccid-driver.h" #endif #include "../common/asshelp.h" #include "../common/server-help.h" /* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN. That * length needs to small compared to the maximum Assuan line length. */ #define MAXLEN_PIN 100 /* Maximum allowed size of key data as used in inquiries. */ #define MAXLEN_KEYDATA 4096 /* Maximum allowed total data size for SETDATA. */ #define MAXLEN_SETDATA 4096 /* Maximum allowed size of certificate data as used in inquiries. */ #define MAXLEN_CERTDATA 16384 /* Maximum allowed size for "SETATTR --inquire". */ #define MAXLEN_SETATTRDATA 16384 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) #define IS_LOCKED(c) (locked_session && locked_session != (c)->server_local) /* Data used to associate an Assuan context with local server data. This object describes the local properties of one session. */ struct server_local_s { /* We keep a list of all active sessions with the anchor at SESSION_LIST (see below). This field is used for linking. */ struct server_local_s *next_session; /* This object is usually assigned to a CTRL object (which is globally visible). While enumerating all sessions we sometimes need to access data of the CTRL object; thus we keep a backpointer here. */ ctrl_t ctrl_backlink; /* The Assuan context used by this session/server. */ assuan_context_t assuan_ctx; #ifdef HAVE_W32_SYSTEM void *event_signal; /* Or NULL if not used. */ #else int event_signal; /* Or 0 if not used. */ #endif /* True if the card has been removed and a reset is required to continue operation. */ unsigned int card_removed:1; /* If set to true we will be terminate ourself at the end of the this session. */ unsigned int stopme:1; /* If set to true, status change will be reported. */ unsigned int watching_status:1; }; /* To keep track of all running sessions, we link all active server contexts and the anchor in this variable. */ static struct server_local_s *session_list; /* If a session has been locked we store a link to its server object in this variable. */ static struct server_local_s *locked_session; /* Local prototypes. */ static int command_has_option (const char *cmd, const char *cmdopt); /* Convert the STRING into a newly allocated buffer while translating the hex numbers. Stops at the first invalid character. Blanks and colons are allowed to separate the hex digits. Returns NULL on error or a newly malloced buffer and its length in LENGTH. */ static unsigned char * hex_to_buffer (const char *string, size_t *r_length) { unsigned char *buffer; const char *s; size_t n; buffer = xtrymalloc (strlen (string)+1); if (!buffer) return NULL; for (s=string, n=0; *s; s++) { if (spacep (s) || *s == ':') continue; if (hexdigitp (s) && hexdigitp (s+1)) { buffer[n++] = xtoi_2 (s); s++; } else break; } *r_length = n; return buffer; } /* Reset the card and free the application context. With SEND_RESET set to true actually send a RESET to the reader; this is the normal way of calling the function. If KEEP_LOCK is set and the session is locked that lock wil not be released. */ static void do_reset (ctrl_t ctrl, int send_reset, int keep_lock) { card_t card = card_get (ctrl, NULL); if (card) { if (!IS_LOCKED (ctrl) && send_reset) card_reset (card); else { ctrl->card_ctx = NULL; ctrl->current_apptype = APPTYPE_NONE; card_unref_locked (card); } card_put (card); } /* If we hold a lock, unlock now. */ if (!keep_lock && locked_session && ctrl->server_local == locked_session) { locked_session = NULL; log_info ("implicitly unlocking due to RESET\n"); } } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); do_reset (ctrl, 1, has_option (line, "--keep-lock")); return 0; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); if (!strcmp (key, "event-signal")) { /* A value of 0 is allowed to reset the event signal. */ #ifdef HAVE_W32_SYSTEM if (!*value) return gpg_error (GPG_ERR_ASS_PARAMETER); #ifdef _WIN64 ctrl->server_local->event_signal = (void *)strtoull (value, NULL, 16); #else ctrl->server_local->event_signal = (void *)strtoul (value, NULL, 16); #endif #else int i = *value? atoi (value) : -1; if (i < 0) return gpg_error (GPG_ERR_ASS_PARAMETER); ctrl->server_local->event_signal = i; #endif } return 0; } /* If the card has not yet been opened, do it. */ static gpg_error_t open_card (ctrl_t ctrl) { /* If we ever got a card not present error code, return that. Only the SERIALNO command and a reset are able to clear from that state. */ if (ctrl->server_local->card_removed) return gpg_error (GPG_ERR_CARD_REMOVED); if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if (ctrl->card_ctx) return 0; return select_application (ctrl, NULL, 0, NULL, 0); } /* Explicitly open a card for a specific use of APPTYPE or SERIALNO. * If OPT_ALL is set also add all possible additional apps. */ static gpg_error_t open_card_with_request (card_t *card_p, ctrl_t ctrl, const char *apptypestr, const char *serialno, int opt_all) { gpg_error_t err; unsigned char *serialno_bin = NULL; size_t serialno_bin_len = 0; card_t card = card_get (ctrl, NULL); if (serialno) serialno_bin = hex_to_buffer (serialno, &serialno_bin_len); /* If we are already initialized for one specific application we need to check that the client didn't requested a specific application different from the one in use before we continue. */ if (apptypestr && card) { err = check_application_conflict (card, apptypestr, serialno_bin, serialno_bin_len); if (gpg_err_code (err) == GPG_ERR_FALSE) { /* Different application but switching is supported. */ err = select_additional_application (card, ctrl, apptypestr); } if (err) card_put (card); goto leave; } /* Re-scan USB devices. Release CARD, before the scan. */ if (card) { ctrl->card_ctx = NULL; ctrl->current_apptype = APPTYPE_NONE; card_unref_locked (card); card_put (card); } err = select_application (ctrl, apptypestr, 1, serialno_bin, serialno_bin_len); card = card_get (ctrl, NULL); if (!err && opt_all) { if (card) { err = select_additional_application (card, ctrl, NULL); if (err) card_put (card); } } leave: if (!err) *card_p = card; xfree (serialno_bin); return err; } static const char hlp_serialno[] = "SERIALNO [--demand=] [--all] []\n" "\n" "Return the serial number of the card using a status response. This\n" "function should be used to check for the presence of a card.\n" "\n" "If --demand is given, an application on the card with SERIALNO is\n" "selected and an error is returned if no such card available.\n" "\n" "If --all is given, all possible other applications of the card are\n" "also selected to prepare for things like \"LEARN --force --multi\".\n" "\n" "If APPTYPE is given, an application of that type is selected and an\n" "error is returned if the application is not supported or available.\n" "The default is to auto-select the application using a hardwired\n" "preference system.\n" "\n" "This function is special in that it can be used to reset the card.\n" "Most other functions will return an error when a card change has\n" "been detected and the use of this function is therefore required.\n" "\n" "Background: We want to keep the client clear of handling card\n" "changes between operations; i.e. the client can assume that all\n" "operations are done on the same card unless he calls this function."; static gpg_error_t cmd_serialno (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); struct server_local_s *sl; gpg_error_t err = 0; char *serial; const char *demand; int opt_all = has_option (line, "--all"); card_t card = NULL; int thisslot; if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if ((demand = has_option_name (line, "--demand"))) { if (*demand != '=') return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); line = (char *)++demand; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; } else demand = NULL; line = skip_options (line); /* Clear the remove flag so that the open_card is able to reread it. */ ctrl->server_local->card_removed = 0; err = open_card_with_request (&card, ctrl, *line? line:NULL, demand, opt_all); /* Now clear or set the card_removed flag for all sessions using the * current slot. In the error case make sure that the flag is set * for the current session. */ thisslot = card? card->slot : -1; for (sl=session_list; sl; sl = sl->next_session) { ctrl_t c = sl->ctrl_backlink; if (c && c->card_ctx && c->card_ctx->slot == thisslot) c->server_local->card_removed = err? 1 : 0; } if (err) { ctrl->server_local->card_removed = 1; return err; } serial = card_get_serialno (card); card_put (card); if (!serial) return gpg_error (GPG_ERR_INV_VALUE); err = assuan_write_status (ctx, "SERIALNO", serial); xfree (serial); return err; } static const char hlp_switchcard[] = "SWITCHCARD []\n" "\n" "Make the card with SERIALNO the current card.\n" "The command \"getinfo card_list\" can be used to list\n" "the serial numbers of inserted and known cards. Note\n" "that the command \"SERIALNO\" can be used to refresh\n" "the list of known cards. A simple SERIALNO status\n" "is printed on success."; static gpg_error_t cmd_switchcard (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; unsigned char *sn_bin = NULL; size_t sn_bin_len = 0; if ((err = open_card (ctrl))) return err; line = skip_options (line); if (*line) { sn_bin = hex_to_buffer (line, &sn_bin_len); if (!sn_bin) { err = gpg_error_from_syserror (); goto leave; } } /* Note that an SN_BIN of NULL will only print the status. */ err = app_switch_current_card (ctrl, sn_bin, sn_bin_len); leave: xfree (sn_bin); return err; } static const char hlp_switchapp[] = "SWITCHAPP []\n" "\n" "Make APPNAME the active application for the current card.\n" "Only some cards support switching between application; the\n" "command \"getinfo active_app\" can be used to get a list of\n" "applications which can be switched to. A SERIALNO status\n" "including the active appname is printed on success."; static gpg_error_t cmd_switchapp (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; card_t card; if ((err = open_card (ctrl))) return err; line = skip_options (line); card = card_get (ctrl, NULL); if (card) { err = app_switch_active_app (card, ctrl, line); card_put (card); } else err = gpg_error (GPG_ERR_CARD_NOT_PRESENT); return err; } static const char hlp_learn[] = "LEARN [--force] [--keypairinfo] [--reread] [--multi]\n" "\n" "Learn all useful information of the currently inserted card. When\n" "used without the force options, the command might do an INQUIRE\n" "like this:\n" "\n" " INQUIRE KNOWNCARDP \n" "\n" "The client should just send an \"END\" if the processing should go on\n" "or a \"CANCEL\" to force the function to terminate with a Cancel\n" "error message.\n" "\n" "With the option --keypairinfo only KEYPAIRINFO status lines are\n" "returned. With the option --reread information from the card are\n" "read again without the need for a reset (sone some cards).\n" "\n" "The response of this command is a list of status lines formatted as\n" "this:\n" "\n" " S APPTYPE \n" "\n" "This returns the type of the application, currently the strings:\n" "\n" " P15 = PKCS-15 structure used\n" " DINSIG = DIN SIG\n" " OPENPGP = OpenPGP card\n" " PIV = PIV card\n" " NKS = NetKey card\n" "\n" "are implemented. These strings are aliases for the AID. With option\n" "--multi information for all switchable apps are returned.\n" "\n" " S KEYPAIRINFO [] [] []\n" "\n" "If there is no certificate yet stored on the card a single 'X' is\n" "returned as the keygrip. For more info see doc/DETAILS. In addition\n" "to the keypair info, information about all certificates stored on the\n" "card is also returned:\n" "\n" " S CERTINFO [