diff --git a/src/agent.c b/src/agent.c
index edadf35..476d0e1 100644
--- a/src/agent.c
+++ b/src/agent.c
@@ -1,1132 +1,1135 @@
/* agent.c - Talking to gpg-agent.
* Copyright (C) 2006, 2007, 2008, 2015, 2019 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
#ifdef HAVE_W32_SYSTEM
# define PATHSEP_C ';'
# define WINVER 0x0500 /* Required for AllowSetForegroundWindow. */
# include
# include
#else
# define PATHSEP_C ':'
#endif
#include
#include
#include "debug.h"
#include "support.h"
#include "sexp-parse.h"
#include "cert.h"
#include "agent.h"
/* The global agent context. */
static assuan_context_t agent_ctx;
/* Hack required for Windows. */
void
gnupg_allow_set_foregound_window (pid_t pid)
{
if (!pid || pid == (pid_t)(-1))
return;
#ifdef HAVE_W32_SYSTEM
else if (!AllowSetForegroundWindow (pid))
DEBUG (DBG_CRIT, "AllowSetForegroundWindow(%lu) failed: %u\n",
(unsigned long)pid, (unsigned int)GetLastError ());
#endif
}
/* Establish a connection to a running GPG agent. */
static gpg_error_t
agent_connect (assuan_context_t *ctx_r)
{
gpg_error_t err = 0;
assuan_context_t ctx = NULL;
char buffer[512];
#ifndef HAVE_W32_SYSTEM
DEBUG (DBG_INFO, "agent_connect: uid=%lu euid=%lu",
(unsigned long)getuid (), (unsigned long)geteuid ());
#endif
/* Use gpgconf to make sure that gpg-agent is started and to obtain
* the socket name. For older version of gnupg we will fallback to
* using two gpgconf commands with the same effect. */
/* FIXME: We should make sure that USER has no spaces. */
snprintf (buffer, sizeof buffer, "%s %s%s --show-socket --launch gpg-agent",
get_gpgconf_path (),
_scute_opt.user? "--chuid=":"",
_scute_opt.user? _scute_opt.user:"");
err = read_first_line (buffer, buffer, sizeof buffer);
if (gpg_err_code (err) == GPG_ERR_NO_AGENT && is_gnupg_older_than (2, 2, 14))
{
snprintf (buffer, sizeof buffer, "%s --launch gpg-agent",
get_gpgconf_path ());
err = read_first_line (buffer, NULL, 0);
if (!err)
{
snprintf (buffer, sizeof buffer, "%s --list-dirs agent-socket",
get_gpgconf_path ());
err = read_first_line (buffer, buffer, sizeof buffer);
}
}
DEBUG (DBG_INFO, "agent_connect: agent socket is '%s'", buffer);
/* Then connect to the socket we got. */
if (!err)
{
err = assuan_new (&ctx);
if (!err)
{
err = assuan_socket_connect (ctx, buffer, 0, 0);
if (!err)
{
*ctx_r = ctx;
if (_scute_opt.debug_flags & DBG_ASSUAN)
assuan_set_log_stream (*ctx_r, _scute_debug_stream);
}
else
assuan_release (ctx);
}
}
/* We do not try any harder. If gpg-connect-agent somehow failed
* to give us a suitable socket, we probably cannot do better. */
if (err)
DEBUG (DBG_CRIT, "cannot connect to GPG agent: %s", gpg_strerror (err));
return err;
}
/*
* Check whether STRING starts with KEYWORD. The keyword is
* delimited by end of string, a space or a tab. Returns NULL if not
* found or a pointer into STRING to the next non-space character
* after the KEYWORD (which may be end of string).
*/
static char *
has_leading_keyword (const char *string, const char *keyword)
{
size_t n = strlen (keyword);
if (!strncmp (string, keyword, n)
&& (!string[n] || string[n] == ' ' || string[n] == '\t'))
{
string += n;
while (*string == ' ' || *string == '\t')
string++;
return (char*)string;
}
return NULL;
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
const char *s;
(void)opaque;
if ((s = has_leading_keyword (line, "PINENTRY_LAUNCHED")))
{
gnupg_allow_set_foregound_window ((pid_t)strtoul (s, NULL, 10));
/* We do not pass errors to avoid breaking other code. */
}
else
DEBUG (DBG_CRIT, "ignoring gpg-agent inquiry `%s'\n", line);
return 0;
}
/* Send a simple command to the agent. */
static gpg_error_t
agent_simple_cmd (assuan_context_t ctx, const char *fmt, ...)
{
gpg_error_t err;
char *optstr;
va_list arg;
int res;
va_start (arg, fmt);
res = vasprintf (&optstr, fmt, arg);
va_end (arg);
if (res < 0)
return gpg_error_from_errno (errno);
err = assuan_transact (ctx, optstr, NULL, NULL, default_inq_cb,
NULL, NULL, NULL);
if (err)
DEBUG (DBG_CRIT, "gpg-agent command '%s' failed: %s", optstr,
gpg_strerror (err));
free (optstr);
return err;
}
/* Configure the GPG agent at connection CTX. */
static gpg_error_t
agent_configure (assuan_context_t ctx)
{
gpg_error_t err = 0;
char *dft_display = NULL;
char *dft_ttyname = NULL;
char *dft_ttytype = NULL;
#if defined(HAVE_SETLOCALE) && (defined(LC_CTYPE) || defined(LC_MESSAGES))
char *old_lc = NULL;
char *dft_lc = NULL;
#endif
char *dft_xauthority = NULL;
char *dft_pinentry_user_data = NULL;
err = agent_simple_cmd (ctx, "RESET");
if (err)
return err;
/* Set up display, terminal and locale options. */
dft_display = getenv ("DISPLAY");
if (dft_display)
err = agent_simple_cmd (ctx, "OPTION display=%s", dft_display);
if (err)
return err;
dft_ttyname = getenv ("GPG_TTY");
if ((!dft_ttyname || !*dft_ttyname) && ttyname (0))
dft_ttyname = ttyname (0);
if (dft_ttyname)
{
err = agent_simple_cmd (ctx, "OPTION ttyname=%s", dft_ttyname);
if (err)
return err;
}
dft_ttytype = getenv ("TERM");
if (dft_ttytype)
err = agent_simple_cmd (ctx, "OPTION ttytype=%s", dft_ttytype);
if (err)
return err;
#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
old_lc = setlocale (LC_CTYPE, NULL);
if (old_lc)
{
old_lc = strdup (old_lc);
if (!old_lc)
return gpg_error_from_errno (errno);
}
dft_lc = setlocale (LC_CTYPE, "");
if (dft_lc)
err = agent_simple_cmd ("OPTION lc-ctype=%s", dft_lc);
if (old_lc)
{
setlocale (LC_CTYPE, old_lc);
free (old_lc);
}
#endif
if (err)
return err;
#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
old_lc = setlocale (LC_MESSAGES, NULL);
if (old_lc)
{
old_lc = strdup (old_lc);
if (!old_lc)
err = gpg_error_from_errno (errno);
}
dft_lc = setlocale (LC_MESSAGES, "");
if (dft_lc)
err = agent_simple_cmd ("OPTION lc-messages=%s", dft_lc);
if (old_lc)
{
setlocale (LC_MESSAGES, old_lc);
free (old_lc);
}
#endif
dft_xauthority = getenv ("XAUTHORITY");
if (dft_xauthority)
err = agent_simple_cmd (ctx, "OPTION xauthority=%s", dft_xauthority);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
err = 0;
else if (err)
return err;
dft_pinentry_user_data = getenv ("PINENTRY_USER_DATA");
if (dft_pinentry_user_data)
err = agent_simple_cmd (ctx, "OPTION pinentry_user_data=%s",
dft_pinentry_user_data);
if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION)
return err;
err = agent_simple_cmd (ctx, "OPTION allow-pinentry-notify");
if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION)
return err;
return err;
}
/* Check for a broken pipe, that is a lost connection to the agent.
* Update the gloabls so that a re-connect is done the next time.
* Returns ERR or the modified code GPG_ERR_NO_AGENT. */
static gpg_error_t
check_broken_pipe (gpg_error_t err)
{
/* Note that Scute _currently_ uses GPG_ERR_SOURCE_ANY. */
if (gpg_err_code (err) == GPG_ERR_EPIPE
&& gpg_err_source (err) == GPG_ERR_SOURCE_ANY)
{
DEBUG (DBG_INFO, "Broken connection to the gpg-agent");
scute_agent_finalize ();
err = gpg_error (GPG_ERR_NO_AGENT);
}
return err;
}
/* If the connection to the agent was lost earlier and detected by
* check_broken_pipe we try to reconnect. */
static gpg_error_t
ensure_agent_connection (void)
{
gpg_error_t err;
if (agent_ctx)
return 0; /* Connection still known. */
DEBUG (DBG_INFO, "Re-connecting to gpg-agent");
err = agent_connect (&agent_ctx);
if (err)
return err;
err = agent_configure (agent_ctx);
return check_broken_pipe (err);
}
/* Try to connect to the agent via socket. Handle the server's
initial greeting. This is used only once when SCute is loaded.
Re-connection is done using ensure_agent_connection. */
gpg_error_t
scute_agent_initialize (void)
{
gpg_error_t err = 0;
if (agent_ctx)
{
DEBUG (DBG_CRIT, "GPG Agent connection already established");
return 0;
}
DEBUG (DBG_INFO, "Establishing connection to gpg-agent");
err = agent_connect (&agent_ctx);
if (err)
return err;
err = agent_configure (agent_ctx);
if (err)
scute_agent_finalize ();
return err;
}
/* Call the agent to get the list of devices. */
gpg_error_t
scute_agent_serialno (void)
{
gpg_error_t err = 0;
err = ensure_agent_connection ();
if (err)
return err;
err = assuan_transact (agent_ctx, "SCD SERIALNO --all",
NULL, NULL, NULL, NULL,
NULL, NULL);
return err;
}
struct keyinfo_parm {
int require_card;
gpg_error_t error;
struct keyinfo *list;
};
/* Callback function for agent_keyinfo_list. */
static gpg_error_t
keyinfo_list_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct keyinfo_parm *parm = opaque;
const char *keyword = line;
int keywordlen;
struct keyinfo *keyinfo = NULL;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen))
{
const char *s;
int n;
struct keyinfo **l_p = &parm->list;
/* It's going to append the information at the end. */
while ((*l_p))
l_p = &(*l_p)->next;
keyinfo = calloc (1, sizeof *keyinfo);
if (!keyinfo)
goto alloc_error;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (n != 40)
goto parm_error;
memcpy (keyinfo->grip, line, 40);
keyinfo->grip[40] = 0;
line = s;
if (!*line)
goto parm_error;
while (spacep (line))
line++;
if (*line++ != 'T')
{
if (parm->require_card)
{
/* It's not on card, skip the status line. */
free (keyinfo);
return 0;
}
else
goto parm_error;
}
if (!*line)
goto parm_error;
while (spacep (line))
line++;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n)
goto skip;
keyinfo->serialno = malloc (n+1);
if (!keyinfo->serialno)
goto alloc_error;
memcpy (keyinfo->serialno, line, n);
keyinfo->serialno[n] = 0;
line = s;
skip:
*l_p = keyinfo;
}
return err;
alloc_error:
free (keyinfo->serialno);
free (keyinfo);
if (!parm->error)
parm->error = gpg_error_from_syserror ();
return 0;
parm_error:
free (keyinfo);
if (!parm->error)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
return 0;
}
void
scute_agent_free_keyinfo (struct keyinfo *l)
{
struct keyinfo *l_next;
for (; l; l = l_next)
{
l_next = l->next;
free (l->serialno);
free (l);
}
}
gpg_error_t
scute_agent_keyinfo_list (struct keyinfo **keyinfo_p)
{
gpg_error_t err;
err = ensure_agent_connection ();
if (!err)
{
struct keyinfo_parm parm;
parm.require_card = 1;
parm.error = 0;
parm.list = NULL;
err = assuan_transact (agent_ctx, "KEYINFO --list",
NULL, NULL, /* No data call back */
NULL, NULL, /* No inquiry call back */
keyinfo_list_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*keyinfo_p = parm.list;
else
scute_agent_free_keyinfo (parm.list);
}
return err;
}
/* 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 *src)
{
char *buffer;
char *dst;
buffer = malloc (strlen (src) + 1);
if (!buffer)
return NULL;
dst = buffer;
while (*src)
{
if (*src == '%' && src[1] && src[2])
{
src++;
*dst = xtoi_2 (src);
if (*dst == '\0')
*dst = '\xff';
dst++;
src += 2;
}
else if (*src == '+')
{
*(dst++) = ' ';
src++;
}
else
*(dst++) = *(src++);
}
*dst = 0;
return buffer;
}
/* We only support RSA signatures up to 4096 bits. */
#define MAX_SIGNATURE_BITS 4096
/* Enough space to hold a 4096 bit RSA signature in an S-expression. */
#define MAX_SIGNATURE_LEN 640 /* FIXME: magic value */
struct signature
{
unsigned char data[MAX_SIGNATURE_LEN];
int len;
};
static gpg_error_t
pksign_cb (void *opaque, const void *buffer, size_t length)
{
struct signature *sig = opaque;
if (sig->len + length > MAX_SIGNATURE_LEN)
{
DEBUG (DBG_INFO, "maximum signature length exceeded");
return gpg_error (GPG_ERR_BAD_DATA);
}
memcpy (&sig->data[sig->len], buffer, length);
sig->len += length;
return 0;
}
/* FIXME: Support ECC */
/* Parse the result of an pksign operation which is a s-expression in
canonical form that looks like (7:sig-val(3:rsa(1:s:))).
The raw result is stored in RESULT of size *LEN, and *LEN is
adjusted to the actual size. */
static gpg_error_t
pksign_parse_result (const struct signature *sig,
unsigned char *result, unsigned int *len)
{
gpg_error_t err;
const unsigned char *s = sig->data;
size_t n;
int depth;
if (*s++ != '(')
gpg_error (GPG_ERR_INV_SEXP);
n = snext (&s);
if (! n)
return gpg_error (GPG_ERR_INV_SEXP);
if (! smatch (&s, n, "sig-val"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s++ != '(')
gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
if (! n)
return gpg_error (GPG_ERR_INV_SEXP);
if (! smatch (&s, n, "rsa"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s++ != '(')
gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
if (! n)
return gpg_error (GPG_ERR_INV_SEXP);
if (! smatch (&s, n, "s"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
if (! n)
return gpg_error (GPG_ERR_INV_SEXP);
/* Remove a possible prepended zero byte. */
if (!*s && n > 1)
{
n -= 1;
s += 1;
}
if (*len < (unsigned int) n)
return gpg_error (GPG_ERR_INV_LENGTH);
*len = (unsigned int) n;
memcpy (result, s, n);
s += n;
depth = 3;
err = sskip (&s, &depth);
if (err)
return err;
if (s - sig->data != sig->len || depth != 0)
return gpg_error (GPG_ERR_INV_SEXP);
return 0;
}
/* Decodes the hash DATA of size LEN (if necessary). Returns a
pointer to the raw hash data in R_DATA, the size in R_LEN, and the
name of the hash function in R_HASH.
Prior to TLSv1.2, the hash function was the concatenation of MD5
and SHA1 applied to the data respectively, and no encoding was
applied. From TLSv1.2 on, the hash value is prefixed with an hash
identifier and encoded using ASN1.
FIXME: Reference. */
static gpg_error_t
decode_hash (const unsigned char *data, int len,
const unsigned char **r_data, size_t *r_len,
const char **r_hash)
{
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */
{ 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04,
0x1C };
static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */
{ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20 };
static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */
{ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
0x00, 0x04, 0x30 };
static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */
{ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40 };
#define HANDLE(hash,hashlen) \
if (len == sizeof hash ## _prefix + (hashlen) \
&& !memcmp (data, hash ## _prefix, sizeof hash ## _prefix)) \
{ \
*r_data = data + sizeof hash ## _prefix; \
*r_len = hashlen; \
*r_hash = #hash; \
}
if (len == 36)
{
/* Prior to TLSv1.2, a combination of MD5 and SHA1 was used. */
*r_data = data;
*r_len = 36;
*r_hash = "tls-md5sha1";
}
/* TLSv1.2 encodes the hash value using ASN1. */
else HANDLE (sha1, 20)
else HANDLE (rmd160, 20)
else HANDLE (sha224, 28)
else HANDLE (sha256, 32)
else HANDLE (sha384, 48)
else HANDLE (sha512, 64)
else
return gpg_error (GPG_ERR_INV_ARG);
#undef HANDLE
return 0;
}
struct sethash_inq_parm_s
{
assuan_context_t ctx;
const void *data;
size_t datalen;
};
/* This is the inquiry callback required by the SETHASH command. */
static gpg_error_t
sethash_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct sethash_inq_parm_s *parm = opaque;
if (has_leading_keyword (line, "TBSDATA"))
{
err = assuan_send_data (parm->ctx, parm->data, parm->datalen);
}
else
err = default_inq_cb (opaque, line);
return err;
}
-/* FIXME: Support ECC */
/* Call the agent to sign (DATA,LEN) using the key described by
* HEXGRIP. Stores the signature in SIG_RESULT and its length at
* SIG_LEN; SIGLEN must initially point to the allocated size of
* SIG_RESULT. */
gpg_error_t
scute_agent_sign (const char *hexgrip, CK_MECHANISM_TYPE mechtype,
unsigned char *data, int len,
unsigned char *sig_result, unsigned int *sig_len)
{
char cmd[150];
gpg_error_t err;
const char *hash;
const unsigned char *raw_data;
size_t raw_len;
#define MAX_DATA_LEN 64 /* Size of an SHA512 sum. */
unsigned char pretty_data[2 * MAX_DATA_LEN + 1];
int i;
struct signature sig;
- int nopadding = (mechtype == CKM_RSA_X_509);
-
+ int nopadding = (mechtype != CKM_RSA_PKCS);
sig.len = 0;
if (sig_len == NULL)
return gpg_error (GPG_ERR_INV_ARG);
if (nopadding)
{
raw_data = data;
raw_len = len;
hash = NULL;
}
else
{
err = decode_hash (data, len, &raw_data, &raw_len, &hash);
if (err)
return err;
}
if (sig_result == NULL)
{
*sig_len = raw_len;
return 0;
}
if (!hexgrip || !sig_result)
return gpg_error (GPG_ERR_INV_ARG);
snprintf (cmd, sizeof (cmd), "SIGKEY %s", hexgrip);
err = ensure_agent_connection ();
if (err)
return err;
err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb,
NULL, NULL, NULL);
err = check_broken_pipe (err);
if (err)
return err;
if (nopadding)
{
struct sethash_inq_parm_s parm;
+ const char more_option = "";
parm.ctx = agent_ctx;
parm.data = raw_data;
parm.datalen = raw_len;
- snprintf (cmd, sizeof (cmd), "SETHASH %s --inquire",
- (raw_len && raw_data[raw_len -1] == 0xBC)? "--pss":"");
+
+ if (mechtype == CKM_RSA_X_509
+ && raw_len && raw_data[raw_len -1] == 0xBC)
+ more_option = "--pss";
+
+ snprintf (cmd, sizeof (cmd), "SETHASH %s --inquire", more_option);
err = assuan_transact (agent_ctx, cmd, NULL, NULL,
sethash_inq_cb, &parm, NULL, NULL);
}
else
{
for (i = 0; i < raw_len; i++)
snprintf (&pretty_data[2 * i], 3, "%02X", raw_data[i]);
pretty_data[2 * raw_len] = '\0';
snprintf (cmd, sizeof (cmd), "SETHASH --hash=%s %s", hash, pretty_data);
err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb,
NULL, NULL, NULL);
}
err = check_broken_pipe (err);
if (err)
return err;
err = assuan_transact (agent_ctx, "PKSIGN",
pksign_cb, &sig, default_inq_cb, NULL, NULL, NULL);
err = check_broken_pipe (err);
if (err)
return err;
err = pksign_parse_result (&sig, sig_result, sig_len);
return err;
}
struct pkdecrypt_parm_s
{
unsigned int len;
unsigned char data[512];
assuan_context_t ctx;
const unsigned char *ciphertext;
size_t ciphertextlen;
};
static gpg_error_t
pkdecrypt_data_cb (void *opaque, const void *buffer, size_t length)
{
struct pkdecrypt_parm_s *parm = opaque;
if (parm->len + length > sizeof parm->data)
{
DEBUG (DBG_INFO, "maximum decryption result length exceeded");
return gpg_error (GPG_ERR_BAD_DATA);
}
memcpy (parm->data + parm->len, buffer, length);
parm->len += length;
return 0;
}
/* Handle the inquiries from pkdecrypt. Note, we only send the data,
* assuan_transact takes care of flushing and writing the "END". */
static gpg_error_t
pkdecrypt_inq_cb (void *opaque, const char *line)
{
struct pkdecrypt_parm_s *parm = opaque;
gpg_error_t err;
const char *keyword = line;
int keywordlen;
for (keywordlen = 0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 10 && !memcmp (keyword, "CIPHERTEXT", 10))
err = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen);
else
err = default_inq_cb (NULL, line);
return err;
}
/* Parse the result of a pkdecrypt operation which is an s-expression
* in canonical form that looks like
* (5:value:).
*
* The raw result is stored in RESULT which has a size of *R_LEN, and
* *R_LEN is adjusted to the actual size. */
static gpg_error_t
pkdecrypt_parse_result (struct pkdecrypt_parm_s *ctx,
unsigned char *result, unsigned int *r_len)
{
char *buf = ctx->data;
size_t len = ctx->len;
char *endp, *raw;
size_t n, rawlen;
if (len < 13 || memcmp (buf, "(5:value", 8) )
return gpg_error (GPG_ERR_INV_SEXP);
len -= 8;
buf += 8;
n = strtoul (buf, &endp, 10);
if (!n || *endp != ':')
return gpg_error (GPG_ERR_INV_SEXP);
endp++;
if ((endp-buf)+n > len)
return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
/* Let (RAW,RAWLEN) describe the pkcs#1 block and remove that padding. */
raw = endp;
rawlen = n;
if (rawlen < 10) /* 0x00 + 0x02 + <1_random> + 0x00 + <16-session> */
return gpg_error (GPG_ERR_INV_SESSION_KEY);
if (raw[0] || raw[1] != 2 ) /* Wrong block type version. */
return gpg_error (GPG_ERR_INV_SESSION_KEY);
for (n=2; n < rawlen && raw[n]; n++) /* Skip the random bytes. */
;
if (n+1 >= rawlen || raw[n] )
return gpg_error (GPG_ERR_INV_SESSION_KEY);
n++; /* Skip the zero byte */
if (*r_len < (rawlen - n))
return gpg_error (GPG_ERR_TOO_LARGE);
memcpy (result, raw + n, rawlen - n);
*r_len = rawlen - n;
return 0;
}
/* Call the agent to decrypt (ENCDATA,ENCDATALEN) using the key
* described by HEXGRIP. Stores the plaintext at R_PLAINDATA and its
* length at R_PLAINDATALEN; R_PLAINDATALEN must initially point to
* the allocated size of R_PLAINDATA and is updated to the actual used
* size on return. */
gpg_error_t
scute_agent_decrypt (const char *hexgrip,
unsigned char *encdata, int encdatalen,
unsigned char *r_plaindata, unsigned int *r_plaindatalen)
{
char cmd[150];
gpg_error_t err;
struct pkdecrypt_parm_s pkdecrypt;
char *s_data;
size_t s_datalen;
if (!hexgrip || !encdata || !encdatalen || !r_plaindatalen)
return gpg_error (GPG_ERR_INV_ARG);
if (!r_plaindata)
{
/* Fixme: We do not return the minimal required length but our
* internal buffer size. */
pkdecrypt.len = *r_plaindatalen;
*r_plaindatalen = sizeof pkdecrypt.data - 1;
if (pkdecrypt.len > sizeof pkdecrypt.data - 1)
return gpg_error (GPG_ERR_INV_LENGTH);
return 0;
}
err = ensure_agent_connection ();
if (err)
return err;
snprintf (cmd, sizeof (cmd), "SETKEY %s", hexgrip);
err = assuan_transact (agent_ctx, cmd,
NULL, NULL,
default_inq_cb, NULL,
NULL, NULL);
err = check_broken_pipe (err);
if (err)
return err;
/* Convert the input into an appropriate s-expression as expected by
* gpg-agent which is:
*
* (enc-val
* (flags pkcs1)
* (rsa
* (a VALUE)))
*
* Out of convenience we append a non-counted extra nul to the
* created canonical s-expression.
*/
s_data = malloc (100 + encdatalen);
if (!s_data)
return gpg_error_from_syserror ();
snprintf (s_data, 50, "(7:enc-val(5:flags5:pkcs1)(3:rsa(1:a%d:",
encdatalen);
s_datalen = strlen (s_data);
memcpy (s_data + s_datalen, encdata, encdatalen);
s_datalen += encdatalen;
memcpy (s_data + s_datalen, ")))", 4);
s_datalen += 3;
pkdecrypt.len = 0;
pkdecrypt.ctx = agent_ctx;
pkdecrypt.ciphertext = s_data;
pkdecrypt.ciphertextlen = s_datalen;
err = assuan_transact (agent_ctx, "PKDECRYPT",
pkdecrypt_data_cb, &pkdecrypt,
pkdecrypt_inq_cb, &pkdecrypt,
NULL, NULL);
err = check_broken_pipe (err);
if (!err)
err = pkdecrypt_parse_result (&pkdecrypt, r_plaindata, r_plaindatalen);
free (s_data);
return err;
}
/* Determine if FPR is trusted. */
gpg_error_t
scute_agent_is_trusted (const char *fpr, bool *is_trusted)
{
gpg_error_t err;
bool trusted = false;
char cmd[150];
err = ensure_agent_connection ();
if (err)
return err;
snprintf (cmd, sizeof (cmd), "ISTRUSTED %s", fpr);
err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb,
NULL, NULL, NULL);
err = check_broken_pipe (err);
if (err && gpg_err_code (err) != GPG_ERR_NOT_TRUSTED)
return err;
else if (!err)
trusted = true;
*is_trusted = trusted;
return 0;
}
#define GET_CERT_INIT_SIZE 2048
struct get_cert_s
{
unsigned char *cert_der;
int cert_der_len;
int cert_der_size;
};
struct random_request
{
unsigned char *buffer;
size_t len;
};
gpg_error_t
get_challenge_data_cb (void *opaque, const void *line, size_t len)
{
struct random_request *request = opaque;
if (len != request->len)
return gpg_error (GPG_ERR_INV_LENGTH);
memcpy (request->buffer, line, len);
return 0;
}
gpg_error_t
scute_agent_get_random (unsigned char *data, size_t len)
{
char command[16];
gpg_error_t err;
struct random_request request;
err = ensure_agent_connection ();
if (err)
return err;
snprintf (command, sizeof(command), "SCD RANDOM %zu", len);
request.buffer = data;
request.len = len;
err = assuan_transact (agent_ctx, command, get_challenge_data_cb,
&request, NULL, NULL, NULL, NULL);
err = check_broken_pipe (err);
return err;
}
void
scute_agent_finalize (void)
{
if (!agent_ctx)
{
DEBUG (DBG_CRIT, "no GPG Agent connection established");
return;
}
DEBUG (DBG_INFO, "releasing agent context");
assuan_release (agent_ctx);
agent_ctx = NULL;
}
diff --git a/src/p11-signinit.c b/src/p11-signinit.c
index 5f3f0b3..d6786fa 100644
--- a/src/p11-signinit.c
+++ b/src/p11-signinit.c
@@ -1,64 +1,59 @@
/* p11-signinit.c - Cryptoki implementation.
* Copyright (C) 2006 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 "cryptoki.h"
#include "locking.h"
#include "slots.h"
/* Prepare a signature operation. HSESSION is the session's handle.
* PMECHANISM describes the mechanism to be used. HKEY describes the
* key to be used. After calling this function either C_Sign or
* (C_SignUpdate, C_SignFinal) can be used to actually sign the data.
* The preparation is valid until C_Sign or C_SignFinal. */
CK_RV CK_SPEC
C_SignInit (CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
CK_OBJECT_HANDLE hKey)
{
CK_RV err = CKR_OK;
slot_iterator_t slot;
session_iterator_t sid;
if (pMechanism == NULL_PTR)
return CKR_ARGUMENTS_BAD;
if (hKey == CK_INVALID_HANDLE)
return CKR_ARGUMENTS_BAD;
- /* FIXME */
- if (pMechanism->mechanism != CKM_RSA_PKCS
- && pMechanism->mechanism != CKM_RSA_X_509)
- return CKR_MECHANISM_INVALID;
-
err = scute_global_lock ();
if (err)
return err;
err = slots_lookup_session (hSession, &slot, &sid);
if (!err)
err = session_set_signing_key (slot, sid, hKey, pMechanism->mechanism);
scute_global_unlock ();
return err;
}
diff --git a/src/slots.c b/src/slots.c
index f9a6524..459e5fb 100644
--- a/src/slots.c
+++ b/src/slots.c
@@ -1,1196 +1,1196 @@
/* slots.c - Slot management.
* Copyright (C) 2006, 2019 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 "cryptoki.h"
#include "table.h"
#include "error-mapping.h"
#include "slots.h"
#include "agent.h"
#include "support.h"
#include "gpgsm.h"
#include "debug.h"
/* A session is just a slot identifier with a per-slot session
identifier. */
/* Must be power of two. */
#define SLOT_MAX (1 << 15)
#define SESSION_SLOT_MASK (SLOT_MAX - 1)
#define SESSION_SLOT_SHIFT 16
#define SESSION_MAX (1 << SESSION_SLOT_SHIFT)
#define SESSION_ID_MASK (SESSION_MAX - 1)
/* Get slot ID from session. */
#define SESSION_SLOT(session) \
((session >> SESSION_SLOT_SHIFT) & SESSION_SLOT_MASK)
/* Get session ID from session. */
#define SESSION_ID(session) (session & SESSION_ID_MASK)
/* Because the slot is already 1-based, we can make the session 0-based. */
#define SESSION_BUILD_ID(slot, session) \
(((slot & SESSION_SLOT_MASK) << SESSION_SLOT_SHIFT) \
| (session & SESSION_ID_MASK))
/* We use one-based IDs. */
#define OBJECT_ID_TO_IDX(id) (id - 1)
#define OBJECT_IDX_TO_ID(idx) (idx + 1)
struct object
{
CK_ATTRIBUTE_PTR attributes;
CK_ULONG attributes_count;
};
/* A mechanism. */
struct mechanism
{
CK_MECHANISM_TYPE type;
CK_MECHANISM_INFO info;
};
/* We use one-based IDs. */
#define MECHANISM_ID_TO_IDX(id) (id - 1)
#define MECHANISM_IDX_TO_ID(idx) (idx + 1)
/* The session state. */
struct session
{
/* True iff read-write session. */
bool rw;
/* The list of objects for the current search. */
object_iterator_t *search_result;
/* The length of the list of objects for the current search. */
int search_result_len;
/* The signing key. */
CK_OBJECT_HANDLE signing_key;
/* The signing mechanism type. */
CK_MECHANISM_TYPE signing_mechanism_type;
/* The decryption key. */
CK_OBJECT_HANDLE decryption_key;
};
/* The slot status. */
typedef enum
{
SLOT_STATUS_USED = 0,
SLOT_STATUS_DEAD = 1
} slot_status_t;
struct slot
{
/* The slot status. Starts out as 0 (pristine). */
slot_status_t status;
/* The slot login status. Starts out as 0 (public). */
slot_login_t login;
/* True iff a token is present. */
bool token_present;
/* The supported mechanisms. */
scute_table_t mechanisms;
/* The sessions. */
scute_table_t sessions;
/* The objects on the token. */
scute_table_t objects;
/* The keygrip to the key in hex string. */
char grip[41];
/* The serial number. */
char serialno[33];
};
/* The slot table. */
static scute_table_t slot_table;
/* Deallocator for mechanisms. */
static void
mechanism_dealloc (void *data)
{
free (data);
}
/* Allocator for mechanisms. The hook must be a pointer to a CK_FLAGS
that should be a combination of CKF_SIGN and/or CKF_DECRYPT. */
static gpg_error_t
mechanism_alloc (void **data_r, void *hook)
{
struct mechanism *mechanism;
struct slot *slot = hook;
int oid;
CK_MECHANISM_TYPE m = CKM_VENDOR_DEFINED;
/* Register the signing mechanism. */
oid = scute_table_first (slot->objects);
while (!scute_table_last (slot->objects, oid))
{
struct object *object;
CK_ATTRIBUTE_PTR attr;
CK_ULONG attr_count;
CK_ULONG i;
object = scute_table_data (slot->objects, oid);
if (!object)
{
attr = NULL;
break;
}
attr = object->attributes;
attr_count = object->attributes_count;
for (i = 0; i < attr_count; i++)
if (attr[i].type == CKA_ALLOWED_MECHANISMS)
break;
if (i != attr_count)
{
if (attr[i].ulValueLen == sizeof (CK_MECHANISM_TYPE))
memcpy (&m, attr[i].pValue, attr[i].ulValueLen);
break;
}
oid = scute_table_next (slot->objects, oid);
}
if (m == CKM_VENDOR_DEFINED)
return gpg_error (GPG_ERR_BAD_DATA);
mechanism = calloc (1, sizeof (*mechanism));
if (mechanism == NULL)
return gpg_error_from_syserror ();
mechanism->type = m;
if (m == CKM_RSA_PKCS)
{
mechanism->info.ulMinKeySize = 1024;
mechanism->info.ulMaxKeySize = 4096;
}
else
{
mechanism->info.ulMinKeySize = 256;
mechanism->info.ulMaxKeySize = 512;
}
mechanism->info.flags = CKF_HW | CKF_SIGN;
*data_r = mechanism;
return 0;
}
static void
object_dealloc (void *data)
{
struct object *obj = data;
while (0 < obj->attributes_count--)
free (obj->attributes[obj->attributes_count].pValue);
free (obj->attributes);
free (obj);
}
/* Allocator for objects. The hook is currently unused. */
static gpg_error_t
object_alloc (void **data_r, void *hook)
{
struct object *object;
(void) hook;
object = calloc (1, sizeof (*object));
if (object == NULL)
return gpg_error_from_syserror ();
*data_r = object;
return 0;
}
static void
session_dealloc (void *data)
{
struct session *session = data;
if (session->search_result)
free (session->search_result);
free (session);
}
/* Allocator for sessions. The hook is currently unused. */
static gpg_error_t
session_alloc (void **data_r, void *hook)
{
struct session *session;
(void) hook;
session = calloc (1, sizeof (*session));
if (session == NULL)
return gpg_error_from_syserror ();
*data_r = session;
return 0;
}
/* Deallocator for slots. */
static void
slot_dealloc (void *data)
{
struct slot *slot = data;
scute_table_destroy (slot->sessions);
scute_table_destroy (slot->mechanisms);
scute_table_destroy (slot->objects);
free (slot);
}
/* Allocator for slots. The hook does not indicate anything at this
point. */
static gpg_error_t
slot_alloc (void **data_r, void *hook)
{
gpg_error_t err;
struct slot *slot;
(void) hook;
slot = calloc (1, sizeof (*slot));
if (slot == NULL)
return gpg_error_from_syserror ();
err = scute_table_create (&slot->mechanisms, mechanism_alloc,
mechanism_dealloc);
if (err)
goto slot_alloc_out;
err = scute_table_create (&slot->sessions, session_alloc, session_dealloc);
if (err)
goto slot_alloc_out;
err = scute_table_create (&slot->objects, object_alloc, object_dealloc);
if (err)
goto slot_alloc_out;
slot->status = SLOT_STATUS_USED;
slot->token_present = false;
slot->login = SLOT_LOGIN_PUBLIC;
*data_r = slot;
slot_alloc_out:
if (err)
slot_dealloc (slot);
return err;
}
static gpg_error_t add_object (void *hook, CK_ATTRIBUTE_PTR attrp,
CK_ULONG attr_countp);
static gpg_error_t slot_init (slot_iterator_t id);
/* Initialize the slot list. */
CK_RV
scute_slots_initialize (void)
{
gpg_error_t err;
struct keyinfo *keyinfo = NULL;
struct keyinfo *ki;
err = scute_agent_serialno ();
if (err)
{
if (gpg_err_code (err) != GPG_ERR_ENODEV)
return scute_gpg_err_to_ck (err);
}
err = scute_agent_keyinfo_list (&keyinfo);
if (err)
return scute_gpg_err_to_ck (err);
err = scute_table_create (&slot_table, slot_alloc, slot_dealloc);
if (err)
return err;
for (ki = keyinfo; ki; ki = ki->next)
{
struct slot *slot;
int slot_idx;
err = scute_table_alloc (slot_table, &slot_idx, (void **)&slot, NULL);
if (err)
{
scute_slots_finalize ();
break;
}
else
{
memset (slot->serialno, 0, sizeof (slot->serialno));
if (ki->serialno)
{
int len;
len = strlen (ki->serialno);
if (len >= 32)
memcpy (slot->serialno, &ki->serialno[len-32], 32);
else
memcpy (slot->serialno, ki->serialno, len);
}
memcpy (slot->grip, ki->grip, 41);
err = scute_gpgsm_get_cert (slot->grip, add_object, slot);
if (!err)
err = slot_init (slot_idx);
if (err)
{
scute_table_dealloc (slot_table, &slot_idx);
err = 0;
}
}
}
scute_agent_free_keyinfo (keyinfo);
return scute_gpg_err_to_ck (err);
}
void
scute_slots_finalize (void)
{
if (!slot_table)
return;
/* This recursively releases all slots and any objects associated
with them. */
scute_table_destroy (slot_table);
slot_table = NULL;
}
/* Reset the slot SLOT after the token has been removed. */
static void
slot_reset (slot_iterator_t id)
{
struct slot *slot = scute_table_data (slot_table, id);
int oid;
/* This also resets the login state. */
slot_close_all_sessions (id);
oid = scute_table_first (slot->objects);
while (!scute_table_last (slot->objects, oid))
scute_table_dealloc (slot->objects, &oid);
assert (scute_table_used (slot->objects) == 0);
slot->token_present = false;
}
static gpg_error_t
add_object (void *hook, CK_ATTRIBUTE_PTR attrp,
CK_ULONG attr_countp)
{
gpg_error_t err;
struct slot *slot = hook;
struct object *object;
unsigned int oidx;
void *objp;
err = scute_table_alloc (slot->objects, &oidx, &objp, NULL);
if (err)
return err;
object = objp;
object->attributes = attrp;
object->attributes_count = attr_countp;
return 0;
}
/* Initialize the slot after a token has been inserted. */
static gpg_error_t
slot_init (slot_iterator_t id)
{
gpg_error_t err = 0;
struct slot *slot = scute_table_data (slot_table, id);
int idx;
/* FIXME: Perform the rest of the initialization of the
token. */
slot->token_present = true;
err = scute_table_alloc (slot->mechanisms, &idx, NULL, slot);
if (err)
slot_reset (id);
return err;
}
/* Begin iterating over the list of slots. */
CK_RV
slots_iterate_first (slot_iterator_t *slot)
{
*slot = scute_table_first (slot_table);
return CKR_OK;
}
/* Continue iterating over the list of slots. */
CK_RV
slots_iterate_next (slot_iterator_t *slot)
{
*slot = scute_table_next (slot_table, *slot);
return CKR_OK;
}
/* Return true iff the previous slot was the last one. */
bool
slots_iterate_last (slot_iterator_t *slot)
{
return scute_table_last (slot_table, *slot);
}
/* Acquire the slot for the slot ID ID. */
CK_RV
slots_lookup (CK_SLOT_ID id, slot_iterator_t *id_r)
{
struct slot *slot = scute_table_data (slot_table, id);
if (slot == NULL)
return CKR_SLOT_ID_INVALID;
*id_r = id;
return CKR_OK;
}
/* Return true iff a token is present in slot SLOT. */
bool
slot_token_present (slot_iterator_t id)
{
struct slot *slot = scute_table_data (slot_table, id);
return slot->token_present;
}
/* Return the token label. We use the dispserialno here too because
* Firefox prints that value in the prompt ("Stored at:"). */
const char *
slot_token_label (slot_iterator_t id)
{
struct slot *slot = scute_table_data (slot_table, id);
return slot->serialno;
}
/* Get the manufacturer of the token. */
const char *
slot_token_manufacturer (slot_iterator_t id)
{
/* FIXME */
(void)id;
return "scdaemon";
}
/* Get the application used on the token. */
const char *
slot_token_application (slot_iterator_t id)
{
struct slot *slot = scute_table_data (slot_table, id);
if (!slot)
return "[ooops]";
/* slots_update() makes sure this is correct. */
/* FIXME */
return "gpg-agent";
}
/* Get the LSB of serial number of the token. */
const char *
slot_token_serial (slot_iterator_t id)
{
struct slot *slot = scute_table_data (slot_table, id);
return &slot->serialno[16];
}
/* Get the manufacturer of the token. */
void
slot_token_version (slot_iterator_t id, CK_BYTE *hw_major, CK_BYTE *hw_minor,
CK_BYTE *fw_major, CK_BYTE *fw_minor)
{
/* FIXME */
(void)id;
*hw_major = 0;
*hw_minor = 0;
*fw_major = 0;
*fw_minor = 0;
}
/* Get the maximum and minimum pin length. */
void
slot_token_maxpinlen (slot_iterator_t id, CK_ULONG *max, CK_ULONG *min)
{
/* FIXME */
(void)id;
*max = 31;
/* FIXME: This is true at least for the user pin (CHV1 and CHV2). */
*min = 6;
}
/* Get the maximum and the actual pin count. */
void
slot_token_pincount (slot_iterator_t id, int *max, int *len)
{
(void)id;
*max = 3;
/* FIXME */
*len = 1;
}
/* Return the ID of slot SLOT. */
CK_SLOT_ID
slot_get_id (slot_iterator_t slot)
{
return slot;
}
/* Return true if the token supports the GET CHALLENGE operation. */
bool
slot_token_has_rng (slot_iterator_t id)
{
struct slot *slot = scute_table_data (slot_table, id);
(void)slot;
/* FIXME */
return 1;
}
/* Mechanism management. */
/* Begin iterating over the list of mechanisms. */
CK_RV
mechanisms_iterate_first (slot_iterator_t id,
mechanism_iterator_t *mechanism)
{
struct slot *slot = scute_table_data (slot_table, id);
*mechanism = scute_table_first (slot->mechanisms);
return CKR_OK;
}
/* Continue iterating over the list of mechanisms. */
CK_RV
mechanisms_iterate_next (slot_iterator_t id, mechanism_iterator_t *mechanism)
{
struct slot *slot = scute_table_data (slot_table, id);
*mechanism = scute_table_next (slot->mechanisms, *mechanism);
return CKR_OK;
}
/* Return true iff the previous slot was the last one. */
bool
mechanisms_iterate_last (slot_iterator_t id, mechanism_iterator_t *mechanism)
{
struct slot *slot = scute_table_data (slot_table, id);
return scute_table_last (slot->mechanisms, *mechanism);
}
/* Acquire the mechanism TYPE for the slot id ID. */
CK_RV
mechanisms_lookup (slot_iterator_t id, mechanism_iterator_t *mid_r,
CK_MECHANISM_TYPE type)
{
struct slot *slot = scute_table_data (slot_table, id);
int mid = scute_table_first (slot->mechanisms);
while (!scute_table_last (slot->mechanisms, mid))
{
struct mechanism *mechanism = scute_table_data (slot->mechanisms, mid);
if (mechanism->type == type)
{
*mid_r = mid;
return CKR_OK;
}
mid = scute_table_next (slot->mechanisms, mid);
}
return CKR_MECHANISM_INVALID;
}
/* Return the type of mechanism MID in slot ID. */
CK_MECHANISM_TYPE
mechanism_get_type (slot_iterator_t id, mechanism_iterator_t mid)
{
struct slot *slot = scute_table_data (slot_table, id);
struct mechanism *mechanism = scute_table_data (slot->mechanisms, mid);
return mechanism->type;
}
/* Return the info of mechanism MID. */
CK_MECHANISM_INFO_PTR
mechanism_get_info (slot_iterator_t id, mechanism_iterator_t mid)
{
struct slot *slot = scute_table_data (slot_table, id);
struct mechanism *mechanism = scute_table_data (slot->mechanisms, mid);
return &mechanism->info;
}
/* Session management. */
static int active_sessions;
/* Create a new session. */
CK_RV
slot_create_session (slot_iterator_t id, session_iterator_t *session,
bool rw)
{
int err;
struct slot *slot = scute_table_data (slot_table, id);
unsigned int tsid;
void *rawp;
struct session *session_p;
assert (slot);
if (scute_table_used (slot->sessions) == SESSION_MAX)
return CKR_SESSION_COUNT;
if (slot->login == SLOT_LOGIN_SO && !rw)
return CKR_SESSION_READ_WRITE_SO_EXISTS;
err = scute_table_alloc (slot->sessions, &tsid, &rawp, NULL);
if (err)
return scute_sys_to_ck (err);
session_p = rawp;
session_p->rw = rw;
session_p->search_result = NULL;
session_p->search_result_len = 0;
session_p->signing_key = CK_INVALID_HANDLE;
session_p->decryption_key = CK_INVALID_HANDLE;
*session = SESSION_BUILD_ID (id, tsid);
active_sessions++;
return CKR_OK;
}
/* Look up session. */
CK_RV
slots_lookup_session (CK_SESSION_HANDLE sid, slot_iterator_t *id,
session_iterator_t *session_id)
{
CK_RV err;
unsigned int idx = SESSION_SLOT (sid);
unsigned session_idx = SESSION_ID (sid);
struct slot *slot;
/* Verify the slot. */
err = slots_lookup (SESSION_SLOT (sid), id);
if (err)
return err;
*session_id = session_idx;
/* Verify the session. */
slot = scute_table_data (slot_table, idx);
if (!scute_table_data (slot->sessions, session_idx))
return CKR_SESSION_HANDLE_INVALID;
return 0;
}
/* Close the session. */
CK_RV
slot_close_session (slot_iterator_t id, session_iterator_t sid)
{
struct slot *slot = scute_table_data (slot_table, id);
scute_table_dealloc (slot->sessions, &sid);
/* At last session closed, return to public sessions. */
if (!scute_table_used (slot->sessions))
slot->login = SLOT_LOGIN_PUBLIC;
active_sessions--;
return CKR_OK;
}
/* Close all sessions. */
CK_RV
slot_close_all_sessions (slot_iterator_t id)
{
struct slot *slot = scute_table_data (slot_table, id);
int sid = scute_table_first (slot->sessions);
while (!scute_table_last (slot->sessions, sid))
{
slot_close_session (id, sid);
sid = scute_table_next (slot->sessions, sid);
}
assert (scute_table_used (slot->sessions) == 0);
return CKR_OK;
}
/* Get the RW flag from the session SID in slot ID. */
bool
session_get_rw (slot_iterator_t id, session_iterator_t sid)
{
struct slot *slot = scute_table_data (slot_table, id);
struct session *session = scute_table_data (slot->sessions, sid);
return session->rw;
}
/* Get the login state from the slot ID. */
slot_login_t
slot_get_login_status (slot_iterator_t id)
{
struct slot *slot = scute_table_data (slot_table, id);
return slot->login;
}
/* Object management. */
/* Begin iterating over the list of objects. */
CK_RV
objects_iterate_first (slot_iterator_t id, object_iterator_t *object)
{
struct slot *slot = scute_table_data (slot_table, id);
*object = scute_table_first (slot->objects);
return CKR_OK;
}
/* Continue iterating over the list of objects. */
CK_RV
objects_iterate_next (slot_iterator_t id, object_iterator_t *object)
{
struct slot *slot = scute_table_data (slot_table, id);
*object = scute_table_next (slot->objects, *object);
return CKR_OK;
}
/* Return true iff the previous slot was the last one. */
bool
objects_iterate_last (slot_iterator_t id, object_iterator_t *object)
{
struct slot *slot = scute_table_data (slot_table, id);
return scute_table_last (slot->objects, *object);
}
/* Return the max. number of objects in the slot. May overcount
somewhat. */
CK_RV
slot_get_object_count (slot_iterator_t id, int *nr)
{
struct slot *slot = scute_table_data (slot_table, id);
*nr = scute_table_used (slot->objects);
return CKR_OK;
}
/* Get the object information for object OBJECT_ID in slot ID. */
CK_RV
slot_get_object (slot_iterator_t id, object_iterator_t oid,
CK_ATTRIBUTE_PTR *obj, CK_ULONG *obj_count)
{
struct slot *slot = scute_table_data (slot_table, id);
struct object *object = scute_table_data (slot->objects, oid);
if (!object)
return CKR_OBJECT_HANDLE_INVALID;
*obj = object->attributes;
*obj_count = object->attributes_count;
return 0;
}
/* Set the result of a search for session SID in slot ID to
SEARCH_RESULT and SEARCH_RESULT_LEN. */
CK_RV
session_set_search_result (slot_iterator_t id, session_iterator_t sid,
object_iterator_t *search_result,
int search_result_len)
{
struct slot *slot = scute_table_data (slot_table, id);
struct session *session = scute_table_data (slot->sessions, sid);
if (session->search_result && session->search_result != search_result)
free (session->search_result);
session->search_result = search_result;
session->search_result_len = search_result_len;
return 0;
}
/* Get the stored search result for the session SID in slot ID. */
CK_RV
session_get_search_result (slot_iterator_t id, session_iterator_t sid,
object_iterator_t **search_result,
int *search_result_len)
{
struct slot *slot = scute_table_data (slot_table, id);
struct session *session = scute_table_data (slot->sessions, sid);
assert (search_result);
assert (search_result_len);
*search_result = session->search_result;
*search_result_len = session->search_result_len;
return 0;
}
/* Set the signing key for session SID in slot ID to KEY. This is the
* core of C_SignInit. */
CK_RV
session_set_signing_key (slot_iterator_t id, session_iterator_t sid,
object_iterator_t key,
CK_MECHANISM_TYPE mechanism_type)
{
struct slot *slot = scute_table_data (slot_table, id);
struct session *session = scute_table_data (slot->sessions, sid);
CK_RV err;
CK_ATTRIBUTE_PTR attr;
CK_ULONG attr_count;
CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY;
err = slot_get_object (id, key, &attr, &attr_count);
if (err)
return err;
while (attr_count-- > 0)
if (attr->type == CKA_CLASS)
break;
if (attr_count == (CK_ULONG) -1)
return CKR_KEY_HANDLE_INVALID;
if (attr->ulValueLen != sizeof (key_class)
|| memcmp (attr->pValue, &key_class, sizeof (key_class)))
return CKR_KEY_HANDLE_INVALID;
- /* It's the private RSA key object. */
+ /* It's the private key object. */
session->signing_key = key;
session->signing_mechanism_type = mechanism_type;
return 0;
}
/* The core of C_Sign - see there for a description. */
CK_RV
session_sign (slot_iterator_t id, session_iterator_t sid,
CK_BYTE_PTR pData, CK_ULONG ulDataLen,
CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen)
{
struct slot *slot = scute_table_data (slot_table, id);
struct session *session = scute_table_data (slot->sessions, sid);
int rc;
gpg_error_t err;
CK_ATTRIBUTE_PTR attr;
CK_ULONG attr_count;
CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY;
unsigned int sig_len;
CK_BYTE key_id[100];
int i;
if (!session->signing_key)
return CKR_OPERATION_NOT_INITIALIZED;
rc = slot_get_object (id, session->signing_key, &attr, &attr_count);
if (rc)
goto leave;
if (attr_count == (CK_ULONG) -1)
{
rc = CKR_KEY_HANDLE_INVALID;
goto leave;
}
if (attr->ulValueLen != sizeof (key_class)
|| memcmp (attr->pValue, &key_class, sizeof (key_class)))
{
rc = CKR_KEY_HANDLE_INVALID;
goto leave;
}
/* Find the CKA_ID */
for (i = 0; i < attr_count; i++)
if (attr[i].type == CKA_ID)
break;
if (i == attr_count)
{
rc = CKR_GENERAL_ERROR;
goto leave;
}
if (attr[i].ulValueLen >= sizeof key_id - 1)
{
rc = CKR_GENERAL_ERROR;
goto leave;
}
strncpy (key_id, attr[i].pValue, attr[i].ulValueLen);
key_id[attr[i].ulValueLen] = 0;
DEBUG (DBG_INFO, "Found CKA_ID '%s'", key_id);
sig_len = *pulSignatureLen;
err = scute_agent_sign (key_id, session->signing_mechanism_type,
pData, ulDataLen, pSignature, &sig_len);
/* Take care of error codes which are not mapped by default. */
if (gpg_err_code (err) == GPG_ERR_INV_LENGTH)
rc = CKR_BUFFER_TOO_SMALL;
else if (gpg_err_code (err) == GPG_ERR_INV_ARG)
rc = CKR_ARGUMENTS_BAD;
else
rc = scute_gpg_err_to_ck (err);
/* Return the length. */
if (rc == CKR_OK || rc == CKR_BUFFER_TOO_SMALL)
*pulSignatureLen = sig_len;
leave:
if (rc != CKR_OK && rc != CKR_BUFFER_TOO_SMALL)
session->signing_key = 0;
return rc;
}
/* Prepare a decryption for slot with SLOTID and the session SID using
* MECHANISM and KEY. This is the core of C_DecryptInit. */
CK_RV
session_init_decrypt (slot_iterator_t slotid, session_iterator_t sid,
CK_MECHANISM *mechanism, object_iterator_t key)
{
struct slot *slot;
struct session *session;
CK_RV rv;
CK_ATTRIBUTE_PTR attr;
CK_ULONG attr_count;
CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY;
if (mechanism->mechanism != CKM_RSA_PKCS)
return CKR_MECHANISM_INVALID;
slot = scute_table_data (slot_table, slotid);
session = scute_table_data (slot->sessions, sid);
rv = slot_get_object (slotid, key, &attr, &attr_count);
if (rv)
return rv;
/* FIXME: What kind of strange loop is this? */
while (attr_count-- > 0)
if (attr->type == CKA_CLASS)
break;
if (attr_count == (CK_ULONG) -1)
return CKR_KEY_HANDLE_INVALID;
if (attr->ulValueLen != sizeof (key_class)
|| memcmp (attr->pValue, &key_class, sizeof (key_class)))
return CKR_KEY_HANDLE_INVALID;
/* It's the private RSA key object. */
session->decryption_key = key;
return 0;
}
/* The core of C_Decrypt - see there for a description. */
CK_RV
session_decrypt (slot_iterator_t slotid, session_iterator_t sid,
CK_BYTE *encdata, CK_ULONG encdatalen,
CK_BYTE *r_plaindata, CK_ULONG *r_plaindatalen)
{
CK_RV rv;
gpg_error_t err;
struct slot *slot;
struct session *session;
CK_ATTRIBUTE_PTR attr;
CK_ULONG attr_count;
CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY;
CK_BYTE key_id[100];
int i;
unsigned int plaindatalen;
slot = scute_table_data (slot_table, slotid);
session = scute_table_data (slot->sessions, sid);
if (!session->decryption_key)
return CKR_OPERATION_NOT_INITIALIZED;
rv = slot_get_object (slotid, session->decryption_key, &attr, &attr_count);
if (rv)
goto leave;
if (attr_count == (CK_ULONG) -1)
{
rv = CKR_KEY_HANDLE_INVALID;
goto leave;
}
if (attr->ulValueLen != sizeof (key_class)
|| memcmp (attr->pValue, &key_class, sizeof (key_class)))
{
rv = CKR_KEY_HANDLE_INVALID;
goto leave;
}
/* Find the CKA_ID */
for (i = 0; i < attr_count; i++)
if (attr[i].type == CKA_ID)
break;
if (i == attr_count)
{
rv = CKR_GENERAL_ERROR;
goto leave;
}
if (attr[i].ulValueLen >= sizeof key_id - 1)
{
rv = CKR_GENERAL_ERROR;
goto leave;
}
strncpy (key_id, attr[i].pValue, attr[i].ulValueLen);
key_id[attr[i].ulValueLen] = 0;
DEBUG (DBG_INFO, "Found CKA_ID '%s'", key_id);
plaindatalen = *r_plaindatalen;
err = scute_agent_decrypt (key_id, encdata, encdatalen,
r_plaindata, &plaindatalen);
DEBUG (DBG_INFO, "agent returned gpg error %d datalen=%u", err, plaindatalen);
/* Take care of error codes which are not mapped by default. */
if (gpg_err_code (err) == GPG_ERR_INV_LENGTH)
rv = CKR_BUFFER_TOO_SMALL;
else if (gpg_err_code (err) == GPG_ERR_INV_ARG)
rv = CKR_ARGUMENTS_BAD;
else
rv = scute_gpg_err_to_ck (err);
/* Return the length. */
if (rv == CKR_OK || rv == CKR_BUFFER_TOO_SMALL)
*r_plaindatalen = plaindatalen;
leave:
if (rv != CKR_BUFFER_TOO_SMALL)
session->decryption_key = 0;
DEBUG (DBG_INFO, "leaving decrypt with rv=%lu", rv);
return rv;
}
CK_RV
scute_slots_rescan_if_no_sessions (void)
{
if (active_sessions == 0)
{
scute_slots_finalize ();
scute_slots_initialize ();
}
return CKR_OK;
}