diff options
Diffstat (limited to 'agent')
-rw-r--r-- | agent/Makefile.am | 7 | ||||
-rw-r--r-- | agent/agent.h | 58 | ||||
-rw-r--r-- | agent/call-daemon.c | 3 | ||||
-rw-r--r-- | agent/call-tpm2d.c | 248 | ||||
-rw-r--r-- | agent/command.c | 5 | ||||
-rw-r--r-- | agent/divert-tpm2.c | 145 | ||||
-rw-r--r-- | agent/gpg-agent.c | 12 | ||||
-rw-r--r-- | agent/keyformat.txt | 12 | ||||
-rw-r--r-- | agent/pkdecrypt.c | 8 | ||||
-rw-r--r-- | agent/pksign.c | 16 | ||||
-rw-r--r-- | agent/protect.c | 27 |
11 files changed, 519 insertions, 22 deletions
diff --git a/agent/Makefile.am b/agent/Makefile.am index 2688ba967..ee7c38d9d 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -38,6 +38,12 @@ endif AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) +if HAVE_LIBTSS +tpm2_sources = divert-tpm2.c call-tpm2d.c +else +tpm2_sources = +endif + gpg_agent_SOURCES = \ gpg-agent.c agent.h \ command.c command-ssh.c \ @@ -55,6 +61,7 @@ gpg_agent_SOURCES = \ cvt-openpgp.c cvt-openpgp.h \ call-scd.c \ call-daemon.c \ + $(tpm2_sources) \ learncard.c common_libs = $(libcommon) diff --git a/agent/agent.h b/agent/agent.h index 4d29ce9c9..94dd8b8f8 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -59,6 +59,7 @@ enum daemon_type { DAEMON_SCD, + DAEMON_TPM2D, DAEMON_MAX_TYPE }; @@ -459,6 +460,7 @@ gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); int agent_pk_get_algo (gcry_sexp_t s_key); +int agent_is_tpm2_key(gcry_sexp_t s_key); int agent_key_available (const unsigned char *grip); gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, int *r_keytype, @@ -577,6 +579,52 @@ gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag); void agent_reload_trustlist (void); +/*-- divert-tpm2.c --*/ +#ifdef HAVE_LIBTSS +int divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, + const unsigned char *digest, size_t digestlen, int algo, + const unsigned char *shadow_info, unsigned char **r_sig, + size_t *r_siglen); +int divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len, int *r_padding); +int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t s_skey); +#else /*!HAVE_LIBTSS*/ +static inline int +divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, + const unsigned char *digest, + size_t digestlen, int algo, + const unsigned char *shadow_info, + unsigned char **r_sig, + size_t *r_siglen) +{ + (void)ctrl; (void)desc_text; (void)digest; (void)digestlen; + (void)algo; (void)shadow_info; (void)r_sig; (void)r_siglen; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} +static inline int +divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len, + int *r_padding) +{ + (void)ctrl; (void)desc_text; (void)cipher; (void)shadow_info; + (void)r_buf; (void)r_len; (void)r_padding; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} +static inline int +divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t s_skey) +{ + (void)ctrl; (void)grip; (void)s_key; + return gpg_error (GPG_ERR_NOT_SUPPORTED); +} +#endif /*!HAVE_LIBTSS*/ + + /*-- divert-scd.c --*/ int divert_pksign (ctrl_t ctrl, const char *desc_text, @@ -606,6 +654,16 @@ void agent_daemon_check_aliveness (void); void agent_reset_daemon (ctrl_t ctrl); void agent_kill_daemon (enum daemon_type type); +/*-- call-tpm2d.c --*/ +int agent_tpm2d_writekey (ctrl_t ctrl, unsigned char **shadow_info, + gcry_sexp_t s_skey); +int agent_tpm2d_pksign (ctrl_t ctrl, const unsigned char *digest, + size_t digestlen, const unsigned char *shadow_info, + unsigned char **r_sig, size_t *r_siglen); +int agent_tpm2d_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher, + size_t cipherlen, const unsigned char *shadow_info, + char **r_buf, size_t *r_len); + /*-- call-scd.c --*/ int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), diff --git a/agent/call-daemon.c b/agent/call-daemon.c index 5147f1557..144400875 100644 --- a/agent/call-daemon.c +++ b/agent/call-daemon.c @@ -45,7 +45,8 @@ * same order as given by the daemon_type enum. */ static const int daemon_modules[DAEMON_MAX_TYPE] = { - GNUPG_MODULE_NAME_SCDAEMON + GNUPG_MODULE_NAME_SCDAEMON, + GNUPG_MODULE_NAME_TPM2DAEMON }; /* Definition of module local data of the CTRL structure. */ diff --git a/agent/call-tpm2d.c b/agent/call-tpm2d.c new file mode 100644 index 000000000..6fae5d85a --- /dev/null +++ b/agent/call-tpm2d.c @@ -0,0 +1,248 @@ +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> + +#include "agent.h" +#include <assuan.h> +#include "../common/strlist.h" +#include "../common/sexp-parse.h" +#include "../common/i18n.h" + +static int +start_tpm2d (ctrl_t ctrl) +{ + return daemon_start (DAEMON_TPM2D, ctrl); +} + +static int +unlock_tpm2d (ctrl_t ctrl, gpg_error_t err) +{ + return daemon_unlock (DAEMON_TPM2D, ctrl, err); +} + +static assuan_context_t +daemon_ctx (ctrl_t ctrl) +{ + return daemon_type_ctx (DAEMON_TPM2D, ctrl); +} + +struct inq_parm_s { + assuan_context_t ctx; + gpg_error_t (*getpin_cb)(ctrl_t, const char *, char **); + ctrl_t ctrl; + /* The next fields are used by inq_keydata. */ + const unsigned char *keydata; + size_t keydatalen; + /* following only used by inq_extra */ + const unsigned char *extra; + size_t extralen; + char *pin; +}; + +static gpg_error_t +inq_needpin (void *opaque, const char *line) +{ + struct inq_parm_s *parm = opaque; + char *pin = NULL; + gpg_error_t rc; + const char *s; + + if ((s = has_leading_keyword (line, "NEEDPIN"))) + { + rc = parm->getpin_cb (parm->ctrl, s, &pin); + if (!rc) + rc = assuan_send_data (parm->ctx, pin, strlen(pin)); + parm->pin = pin; + } + else + { + log_error ("unsupported inquiry '%s'\n", line); + rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + } + + return rc; +} + +static gpg_error_t +inq_keydata (void *opaque, const char *line) +{ + struct inq_parm_s *parm = opaque; + + if (has_leading_keyword (line, "KEYDATA")) + return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen); + else + return inq_needpin (opaque, line); +} + +static gpg_error_t +inq_extra (void *opaque, const char *line) +{ + struct inq_parm_s *parm = opaque; + + if (has_leading_keyword (line, "EXTRA")) + return assuan_send_data (parm->ctx, parm->extra, parm->extralen); + else + return inq_keydata (opaque, line); +} + +int +agent_tpm2d_writekey (ctrl_t ctrl, unsigned char **shadow_info, + gcry_sexp_t s_skey) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + size_t n; + unsigned char *kbuf; + membuf_t data; + struct inq_parm_s inqparm; + size_t len; + + rc = start_tpm2d (ctrl); + if (rc) + return rc; + + /* note: returned data is TPM protected so no need for a sensitive context */ + init_membuf(&data, 4096); + + inqparm.ctx = daemon_ctx (ctrl); + inqparm.getpin_cb = agent_ask_new_passphrase; + inqparm.ctrl = ctrl; + inqparm.pin = NULL; + + n = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); + kbuf = xtrymalloc (n); + gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, kbuf, n); + inqparm.keydata = kbuf; + inqparm.keydatalen = n; + snprintf(line, sizeof(line), "IMPORT"); + + rc = assuan_transact (daemon_ctx (ctrl), line, + put_membuf_cb, &data, + inq_keydata, &inqparm, + NULL, NULL); + xfree (kbuf); + xfree (inqparm.pin); + if (rc) + { + xfree (get_membuf (&data, &len)); + return unlock_tpm2d (ctrl, rc); + } + + *shadow_info = get_membuf (&data, &len); + + return unlock_tpm2d (ctrl, 0); +} + +static gpg_error_t +pin_cb (ctrl_t ctrl, const char *prompt, char **passphrase) +{ + *passphrase = agent_get_cache (ctrl, ctrl->keygrip, CACHE_MODE_USER); + if (*passphrase) + return 0; + return agent_get_passphrase(ctrl, passphrase, + _("Please enter your passphrase, so that the " + "secret key can be unlocked for this session"), + prompt, NULL, 0, + ctrl->keygrip, CACHE_MODE_USER, NULL); +} + +int +agent_tpm2d_pksign (ctrl_t ctrl, const unsigned char *digest, + size_t digestlen, const unsigned char *shadow_info, + unsigned char **r_sig, size_t *r_siglen) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct inq_parm_s inqparm; + + rc = start_tpm2d (ctrl); + if (rc) + return rc; + + init_membuf (&data, 1024); + + inqparm.ctx = daemon_ctx (ctrl); + inqparm.getpin_cb = pin_cb; + inqparm.ctrl = ctrl; + inqparm.keydata = shadow_info; + inqparm.keydatalen = gcry_sexp_canon_len (shadow_info, 0, NULL, NULL); + inqparm.extra = digest; + inqparm.extralen = digestlen; + inqparm.pin = NULL; + + snprintf(line, sizeof(line), "PKSIGN"); + + rc = assuan_transact (daemon_ctx (ctrl), line, + put_membuf_cb, &data, + inq_extra, &inqparm, + NULL, NULL); + if (!rc) + agent_put_cache (ctrl, ctrl->keygrip, CACHE_MODE_USER, inqparm.pin, 0); + + xfree (inqparm.pin); + + if (rc) + { + size_t len; + xfree (get_membuf (&data, &len)); + return unlock_tpm2d (ctrl, rc); + } + + *r_sig = get_membuf (&data, r_siglen); + + return unlock_tpm2d (ctrl, 0); +} + +int +agent_tpm2d_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher, + size_t cipherlen, const unsigned char *shadow_info, + char **r_buf, size_t *r_len) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct inq_parm_s inqparm; + + rc = start_tpm2d (ctrl); + if (rc) + return rc; + + init_membuf (&data, 1024); + + inqparm.ctx = daemon_ctx (ctrl); + inqparm.getpin_cb = pin_cb; + inqparm.ctrl = ctrl; + inqparm.keydata = shadow_info; + inqparm.keydatalen = gcry_sexp_canon_len (shadow_info, 0, NULL, NULL); + inqparm.extra = cipher; + inqparm.extralen = cipherlen; + inqparm.pin = NULL; + + snprintf(line, sizeof(line), "PKDECRYPT"); + + rc = assuan_transact (daemon_ctx (ctrl), line, + put_membuf_cb, &data, + inq_extra, &inqparm, + NULL, NULL); + if (!rc) + agent_put_cache (ctrl, ctrl->keygrip, CACHE_MODE_USER, inqparm.pin, 0); + + xfree (inqparm.pin); + + if (rc) + { + size_t len; + xfree (get_membuf (&data, &len)); + return unlock_tpm2d (ctrl, rc); + } + + *r_buf = get_membuf (&data, r_len); + + return unlock_tpm2d (ctrl, 0); +} diff --git a/agent/command.c b/agent/command.c index 8384560cd..87446a233 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1314,6 +1314,11 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, if (err) goto leave; } + else if (strcmp (shadow_info_type, "tpm2-v1") == 0) + { + serialno = xstrdup("TPM-Protected"); + idstr = NULL; + } else { log_error ("unrecognised shadow key type %s\n", shadow_info_type); diff --git a/agent/divert-tpm2.c b/agent/divert-tpm2.c new file mode 100644 index 000000000..4946f7a8a --- /dev/null +++ b/agent/divert-tpm2.c @@ -0,0 +1,145 @@ +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> + +#include "agent.h" +#include "../common/i18n.h" +#include "../common/sexp-parse.h" + +int +divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, + const unsigned char *digest, size_t digestlen, int algo, + const unsigned char *shadow_info, unsigned char **r_sig, + size_t *r_siglen) +{ + return agent_tpm2d_pksign(ctrl, digest, digestlen, + shadow_info, r_sig, r_siglen); +} + + +static gpg_error_t +agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, + unsigned char *shadow_info) +{ + gpg_error_t err; + unsigned char *shdkey; + unsigned char *pkbuf; + size_t len; + gcry_sexp_t s_pkey; + + err = agent_public_key_from_file (ctrl, grip, &s_pkey); + len = gcry_sexp_sprint(s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); + pkbuf = xtrymalloc (len); + gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, pkbuf, len); + gcry_sexp_release (s_pkey); + + err = agent_shadow_key_type (pkbuf, shadow_info, "tpm2-v1", &shdkey); + xfree (pkbuf); + if (err) + { + log_error ("shadowing the key failed: %s\n", gpg_strerror (err)); + return err; + } + + len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); + err = agent_write_private_key (grip, shdkey, len, 1 /*force*/, + NULL, NULL, 0); + xfree (shdkey); + if (err) + log_error ("error writing key: %s\n", gpg_strerror (err)); + + return err; +} + +int +divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t s_skey) +{ + int ret; + /* shadow_info is always shielded so no special handling required */ + unsigned char *shadow_info; + + ret = agent_tpm2d_writekey(ctrl, &shadow_info, s_skey); + if (!ret) { + ret = agent_write_tpm2_shadow_key (ctrl, grip, shadow_info); + xfree (shadow_info); + } + return ret; +} + +int +divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len, int *r_padding) +{ + const unsigned char *s; + size_t n; + + *r_padding = -1; + + (void)desc_text; + + s = cipher; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "enc-val")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "rsa")) + { + *r_padding = 0; + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "a")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + } + else if (smatch (&s, n, "ecdh")) + { + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "s")) + { + n = snext (&s); + s += n; + if (*s++ != ')') + return gpg_error (GPG_ERR_INV_SEXP); + if (*s++ != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + } + if (!smatch (&s, n, "e")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + return agent_tpm2d_pkdecrypt (ctrl, s, n, shadow_info, r_buf, r_len); +} diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index b3a0c230c..74c2108b1 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -102,6 +102,7 @@ enum cmd_and_opt_values oLCmessages, oXauthority, oScdaemonProgram, + oTpm2daemonProgram, oDefCacheTTL, oDefCacheTTLSSH, oMaxCacheTTL, @@ -199,6 +200,8 @@ static gpgrt_opt_t opts[] = { /* */ N_("do not use the SCdaemon") ), ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program", /* */ N_("|PGM|use PGM as the SCdaemon program") ), + ARGPARSE_s_s (oTpm2daemonProgram, "tpm2daemon-program", + /* */ N_("|PGM|use PGM as the tpm2daemon program") ), ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), ARGPARSE_s_s (oExtraSocket, "extra-socket", @@ -905,7 +908,14 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break; break; case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break; - case oScdaemonProgram: opt.daemon_program[DAEMON_SCD] = pargs->r.ret_str; break; + + case oTpm2daemonProgram: + opt.daemon_program[DAEMON_TPM2D] = pargs->r.ret_str; + break; + + case oScdaemonProgram: + opt.daemon_program[DAEMON_SCD] = pargs->r.ret_str; + break; case oDisableScdaemon: opt.disable_daemon[DAEMON_SCD] = 1; break; case oDisableCheckOwnSocket: disable_check_own_socket = 1; break; diff --git a/agent/keyformat.txt b/agent/keyformat.txt index 88c3a2d36..3467f3bc5 100644 --- a/agent/keyformat.txt +++ b/agent/keyformat.txt @@ -312,8 +312,9 @@ to keys stored on a token: (comment whatever) ) -The currently used protocol is "t1-v1" (token info version 1). The -second list with the information has this layout: +The currently used protocols are "t1-v1" (token info version 1) and +"tpm2-v1" (TPM format key information). The second list with the +information has this layout for "t1-v1": (card_serial_number id_string_of_key fixed_pin_length) @@ -322,6 +323,13 @@ the PIN; a value of 0 indicates that this information is not available. The rationale for this field is that some pinpad equipped readers don't allow passing a variable length PIN. +This is the (info) layout for "tpm2-v1": + +(parent tpm_private_string tpm_public_string) + +Although this precise format is encapsulated inside the tpm2daemon +itself and nothing in gpg ever uses this. + More items may be added to the list. ** OpenPGP Private Key Transfer Format diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index da370bb0a..16a15b9d0 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -88,8 +88,12 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, goto leave; } - err = divert_pkdecrypt (ctrl, desc_text, ctrl->keygrip, ciphertext, - shadow_info, &buf, &len, r_padding); + if (agent_is_tpm2_key (s_skey)) + err = divert_tpm2_pkdecrypt (ctrl, desc_text, ciphertext, shadow_info, + &buf, &len, r_padding); + else + err = divert_pkdecrypt (ctrl, desc_text, ctrl->keygrip, ciphertext, + shadow_info, &buf, &len, r_padding); if (err) { log_error ("smartcard decryption failed: %s\n", gpg_strerror (err)); diff --git a/agent/pksign.c b/agent/pksign.c index ca9a35292..00b31ee45 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -397,11 +397,17 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, if (desc_text) agent_modify_description (desc_text, NULL, s_pkey, &desc2); - err = divert_pksign (ctrl, desc2? desc2 : desc_text, - ctrl->keygrip, - data, datalen, - ctrl->digest.algo, - shadow_info, &buf, &len); + if (agent_is_tpm2_key (s_skey)) + err = divert_tpm2_pksign (ctrl, desc2? desc2 : desc_text, + data, datalen, + ctrl->digest.algo, + shadow_info, &buf, &len); + else + err = divert_pksign (ctrl, desc2? desc2 : desc_text, + ctrl->keygrip, + data, datalen, + ctrl->digest.algo, + shadow_info, &buf, &len); xfree (desc2); } if (err) diff --git a/agent/protect.c b/agent/protect.c index 54666e717..76ead444b 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -1679,41 +1679,46 @@ agent_get_shadow_info_type (const unsigned char *shadowkey, return 0; } + gpg_error_t -agent_get_shadow_info(const unsigned char *shadowkey, - unsigned char const **shadow_info) +agent_get_shadow_info (const unsigned char *shadowkey, + unsigned char const **shadow_info) { - return agent_get_shadow_info_type(shadowkey, shadow_info, NULL); + return agent_get_shadow_info_type (shadowkey, shadow_info, NULL); } + int -agent_is_tpm2_key(gcry_sexp_t s_skey) +agent_is_tpm2_key (gcry_sexp_t s_skey) { unsigned char *buf; unsigned char *type; size_t len; gpg_error_t err; - err = make_canon_sexp(s_skey, &buf, &len); + err = make_canon_sexp (s_skey, &buf, &len); if (err) return 0; - err = agent_get_shadow_info_type(buf, NULL, &type); + err = agent_get_shadow_info_type (buf, NULL, &type); if (err) return 0; + xfree (buf); - err = strcmp(type, "tpm2-v1") == 0; - xfree(type); + err = strcmp (type, "tpm2-v1") == 0; + xfree (type); return err; } + gpg_error_t -agent_get_shadow_type(const unsigned char *shadowkey, - unsigned char **shadow_type) +agent_get_shadow_type (const unsigned char *shadowkey, + unsigned char **shadow_type) { - return agent_get_shadow_info_type(shadowkey, NULL, shadow_type); + return agent_get_shadow_info_type (shadowkey, NULL, shadow_type); } + /* Parse the canonical encoded SHADOW_INFO S-expression. On success the hex encoded serial number is returned as a malloced strings at R_HEXSN and the Id string as a malloced string at R_IDSTR. On |