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 [