diff --git a/common/Makefile.am b/common/Makefile.am index 0d5af3e5c..189874ab5 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -1,227 +1,226 @@ # Makefile for common gnupg modules # Copyright (C) 2001, 2003, 2007, 2010 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 . ## Process this file with automake to produce Makefile.in EXTRA_DIST = mkstrtable.awk exaudit.awk exstatus.awk ChangeLog-2011 \ audit-events.h status-codes.h ChangeLog.jnlib \ ChangeLog-2011.include w32info-rc.h.in gnupg.ico \ all-tests.scm noinst_LIBRARIES = libcommon.a libcommonpth.a libgpgrl.a noinst_LIBRARIES += libsimple-pwquery.a noinst_PROGRAMS = $(module_tests) $(module_maint_tests) if DISABLE_TESTS TESTS = else TESTS = $(module_tests) endif BUILT_SOURCES = audit-events.h status-codes.h MAINTAINERCLEANFILES = audit-events.h status-codes.h AM_CPPFLAGS = AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(KSBA_CFLAGS) include $(top_srcdir)/am/cmacros.am common_sources = \ common-defs.h \ util.h utilproto.h fwddecl.h i18n.c i18n.h \ types.h host2net.h dynload.h w32help.h \ mapstrings.c stringhelp.c stringhelp.h \ strlist.c strlist.h \ utf8conv.c utf8conv.h \ logging.h \ dotlock.c dotlock.h \ mischelp.c mischelp.h \ status.c status.h\ shareddefs.h \ openpgpdefs.h \ gc-opt-flags.h \ sexp-parse.h \ tlv.c tlv.h tlv-builder.c \ init.c init.h \ sexputil.c \ sysutils.c sysutils.h \ homedir.c \ gettime.c gettime.h \ yesno.c \ - b64enc.c b64dec.c zb32.c zb32.h \ + zb32.c zb32.h \ convert.c \ percent.c \ mbox-util.c mbox-util.h \ miscellaneous.c \ xasprintf.c \ xreadline.c \ membuf.c membuf.h \ ccparray.c ccparray.h \ iobuf.c iobuf.h \ ttyio.c ttyio.h \ asshelp.c asshelp2.c asshelp.h \ exechelp.h \ signal.c \ audit.c audit.h \ localename.c \ session-env.c session-env.h \ userids.c userids.h \ openpgp-oid.c openpgp-s2k.c \ ssh-utils.c ssh-utils.h \ agent-opt.c \ helpfile.c \ mkdir_p.c mkdir_p.h \ strlist.c strlist.h \ exectool.c exectool.h \ server-help.c server-help.h \ name-value.c name-value.h \ recsel.c recsel.h \ ksba-io-support.c ksba-io-support.h \ openpgp-fpr.c \ comopt.c comopt.h \ compliance.c compliance.h \ pkscreening.c pkscreening.h if HAVE_W32_SYSTEM common_sources += w32-reg.c w32-cmdline.c endif # To make the code easier to read we have split home some code into # separate source files. if HAVE_W32_SYSTEM common_sources += exechelp-w32.c else common_sources += exechelp-posix.c endif # Sources only useful without NPTH. without_npth_sources = \ get-passphrase.c get-passphrase.h # Sources only useful with NPTH. with_npth_sources = \ call-gpg.c call-gpg.h libcommon_a_SOURCES = $(common_sources) $(without_npth_sources) libcommon_a_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) -DWITHOUT_NPTH=1 libcommonpth_a_SOURCES = $(common_sources) $(with_npth_sources) libcommonpth_a_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) libsimple_pwquery_a_SOURCES = \ simple-pwquery.c simple-pwquery.h asshelp.c asshelp.h libsimple_pwquery_a_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) libgpgrl_a_SOURCES = \ gpgrlhelp.c if MAINTAINER_MODE # Note: Due to the dependency on Makefile, the file will always be # rebuilt, so we allow this only in maintainer mode. # Create the audit-events.h include file from audit.h # Note: We create the target file in the source directory because it # is a distributed built source. If we would not do that we may end # up with two files and then it is not clear which version of the # files will be picked up. audit-events.h: Makefile.am mkstrtable.awk exaudit.awk audit.h $(AWK) -f $(srcdir)/exaudit.awk $(srcdir)/audit.h \ | $(AWK) -f $(srcdir)/mkstrtable.awk -v textidx=3 -v nogettext=1 \ -v pkg_namespace=eventstr_ > $(srcdir)/audit-events.h # Create the status-codes.h include file from status.h status-codes.h: Makefile.am mkstrtable.awk exstatus.awk status.h $(AWK) -f $(srcdir)/exstatus.awk $(srcdir)/status.h \ | $(AWK) -f $(srcdir)/mkstrtable.awk -v textidx=3 -v nogettext=1 \ -v pkg_namespace=statusstr_ > $(srcdir)/status-codes.h endif # # Module tests # module_tests = t-stringhelp t-timestuff \ t-convert t-percent t-gettime t-sysutils t-sexputil \ t-session-env t-openpgp-oid t-ssh-utils \ t-mapstrings t-zb32 t-mbox-util t-iobuf t-strlist \ t-name-value t-ccparray t-recsel t-w32-cmdline t-exechelp if HAVE_W32_SYSTEM module_tests += t-w32-reg else module_tests += t-exectool endif if MAINTAINER_MODE -module_maint_tests = t-helpfile t-b64 +module_maint_tests = t-helpfile else module_maint_tests = endif t_extra_src = t-support.h t_common_cflags = $(KSBA_CFLAGS) $(LIBGCRYPT_CFLAGS) \ $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) t_common_ldadd = libcommon.a \ $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) $(NETLIBS) # Common tests t_stringhelp_SOURCES = t-stringhelp.c $(t_extra_src) t_stringhelp_LDADD = $(t_common_ldadd) t_timestuff_SOURCES = t-timestuff.c $(t_extra_src) t_timestuff_LDADD = $(t_common_ldadd) t_convert_LDADD = $(t_common_ldadd) t_percent_LDADD = $(t_common_ldadd) t_gettime_LDADD = $(t_common_ldadd) t_sysutils_LDADD = $(t_common_ldadd) t_helpfile_LDADD = $(t_common_ldadd) t_sexputil_LDADD = $(t_common_ldadd) -t_b64_LDADD = $(t_common_ldadd) t_exechelp_LDADD = $(t_common_ldadd) t_exectool_LDADD = $(t_common_ldadd) t_session_env_LDADD = $(t_common_ldadd) t_openpgp_oid_LDADD = $(t_common_ldadd) t_ssh_utils_LDADD = $(t_common_ldadd) t_mapstrings_LDADD = $(t_common_ldadd) t_zb32_SOURCES = t-zb32.c $(t_extra_src) t_zb32_LDADD = $(t_common_ldadd) t_mbox_util_LDADD = $(t_common_ldadd) t_iobuf_LDADD = $(t_common_ldadd) t_strlist_LDADD = $(t_common_ldadd) t_name_value_LDADD = $(t_common_ldadd) t_ccparray_LDADD = $(t_common_ldadd) t_recsel_LDADD = $(t_common_ldadd) t_w32_cmdline_SOURCES = t-w32-cmdline.c w32-cmdline.c $(t_extra_src) t_w32_cmdline_LDADD = $(t_common_ldadd) # System specific test if HAVE_W32_SYSTEM t_w32_reg_SOURCES = t-w32-reg.c $(t_extra_src) t_w32_reg_LDADD = $(t_common_ldadd) endif # All programs should depend on the created libs. $(PROGRAMS) : libcommon.a libcommonpth.a diff --git a/common/ssh-utils.c b/common/ssh-utils.c index ab29558cf..d27e2e200 100644 --- a/common/ssh-utils.c +++ b/common/ssh-utils.c @@ -1,644 +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; + gpgrt_b64state_t 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) + b64s = gpgrt_b64enc_start (stream, ""); + if (!b64s) { es_fclose (stream); goto leave; } - err = b64enc_write (&b64s, - gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo)); + err = gpgrt_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); + err = gpgrt_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; + gpgrt_b64state_t b64_state; algo = get_pk_algo_from_key (key); if (algo == 0) return gpg_error (GPG_ERR_PUBKEY_ALGO); if (algo == GCRY_PK_ECC || algo == GCRY_PK_EDDSA) { curve = gcry_pk_get_curve (key, 0, NULL); if (!curve) return gpg_error (GPG_ERR_INV_CURVE); } switch (algo) { case GCRY_PK_RSA: identifier = "ssh-rsa"; pub_elements = "en"; break; case GCRY_PK_ECC: if (!strcmp (curve, "NIST P-256")) identifier = "ecdsa-sha2-nistp256"; else if (!strcmp (curve, "NIST P-384")) identifier = "ecdsa-sha2-nistp384"; else if (!strcmp (curve, "NIST P-521")) identifier = "ecdsa-sha2-nistp521"; else err = gpg_error (GPG_ERR_UNKNOWN_CURVE); pub_elements = "q"; break; case GCRY_PK_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) + b64_state = gpgrt_b64enc_start (stream, ""); + if (!b64_state) { es_free (blob); - return err; + return gpg_error_from_syserror (); } - err = b64enc_write (&b64_state, blob, bloblen); - b64enc_finish (&b64_state); + err = gpgrt_b64enc_write (b64_state, blob, bloblen); + gpgrt_b64enc_finish (b64_state); es_free (blob); if (err) return err; if (comment) es_fprintf (stream, " %s", comment); return err; } diff --git a/common/util.h b/common/util.h index 6b948510e..764030ad8 100644 --- a/common/util.h +++ b/common/util.h @@ -1,427 +1,398 @@ /* util.h - Utility functions for GnuPG * Copyright (C) 2001, 2002, 2003, 2004, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute and/or modify this * part of GnuPG 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. * * 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 copies of the GNU General Public License * and the GNU Lesser General Public License along with this program; * if not, see . */ #ifndef GNUPG_COMMON_UTIL_H #define GNUPG_COMMON_UTIL_H #include /* We need this for the memory function protos. */ #include /* We need errno. */ #include /* We need gpg_error_t and estream. */ /* These error codes are used but not defined in the required * libgpg-error version. Define them here. * Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21) */ #ifndef EXTERN_UNLESS_MAIN_MODULE # if !defined (INCLUDED_BY_MAIN_MODULE) # define EXTERN_UNLESS_MAIN_MODULE extern # else # define EXTERN_UNLESS_MAIN_MODULE # endif #endif /* Hash function used with libksba. */ #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write) /* The length of the keygrip. This is a SHA-1 hash of the key * parameters as generated by gcry_pk_get_keygrip. */ #define KEYGRIP_LEN 20 /* The length of the unique blob identifier as used by the keyboxd. * This is the possible truncated fingerprint of the primary key. */ #define UBID_LEN 20 /* Get all the stuff from jnlib. */ #include "../common/logging.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/dotlock.h" #include "../common/utf8conv.h" #include "../common/dynload.h" #include "../common/fwddecl.h" #include "../common/utilproto.h" #include "gettime.h" /* Redefine asprintf by our estream version which uses our own memory allocator.. */ #define asprintf gpgrt_asprintf #define vasprintf gpgrt_vasprintf /* Due to a bug in mingw32's snprintf related to the 'l' modifier and for increased portability we use our snprintf on all systems. */ #undef snprintf #define snprintf gpgrt_snprintf /* Replacements for macros not available with libgpg-error < 1.20. */ /* We need this type even if we are not using libreadline and or we did not include libreadline in the current file. */ #ifndef GNUPG_LIBREADLINE_H_INCLUDED typedef char **rl_completion_func_t (const char *, int, int); #endif /*!GNUPG_LIBREADLINE_H_INCLUDED*/ /* Handy malloc macros - please use only them. */ #define xtrymalloc(a) gcry_malloc ((a)) #define xtrymalloc_secure(a) gcry_malloc_secure ((a)) #define xtrycalloc(a,b) gcry_calloc ((a),(b)) #define xtrycalloc_secure(a,b) gcry_calloc_secure ((a),(b)) #define xtryrealloc(a,b) gcry_realloc ((a),(b)) #define xtryreallocarray(a,b,c,d) gpgrt_reallocarray ((a),(b),(c),(d)) #define xtrystrdup(a) gcry_strdup ((a)) #define xfree(a) gcry_free ((a)) #define xfree_fnc gcry_free #define xmalloc(a) gcry_xmalloc ((a)) #define xmalloc_secure(a) gcry_xmalloc_secure ((a)) #define xcalloc(a,b) gcry_xcalloc ((a),(b)) #define xcalloc_secure(a,b) gcry_xcalloc_secure ((a),(b)) #define xrealloc(a,b) gcry_xrealloc ((a),(b)) #define xstrdup(a) gcry_xstrdup ((a)) /* See also the xreallocarray prototype below. */ /* For compatibility with gpg 1.4 we also define these: */ #define xmalloc_clear(a) gcry_xcalloc (1, (a)) #define xmalloc_secure_clear(a) gcry_xcalloc_secure (1, (a)) /* The default error source of the application. This is different from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the source file and thus is usable in code shared by applications. Defined by init.c. */ extern gpg_err_source_t default_errsource; /* Convenience function to return a gpg-error code for memory allocation failures. This function makes sure that an error will be returned even if accidentally ERRNO is not set. */ static inline gpg_error_t out_of_core (void) { return gpg_error_from_syserror (); } /*-- yesno.c --*/ int answer_is_yes (const char *s); int answer_is_yes_no_default (const char *s, int def_answer); int answer_is_yes_no_quit (const char *s); int answer_is_okay_cancel (const char *s, int def_answer); /*-- xreadline.c --*/ ssize_t read_line (FILE *fp, char **addr_of_buffer, size_t *length_of_buffer, size_t *max_length); - -/*-- b64enc.c and b64dec.c --*/ -struct b64state -{ - unsigned int flags; - int idx; - int quad_count; - FILE *fp; - estream_t stream; - char *title; - unsigned char radbuf[4]; - u32 crc; - int stop_seen:1; - int invalid_encoding:1; - gpg_error_t lasterr; -}; - -gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title); -gpg_error_t b64enc_start_es (struct b64state *state, estream_t fp, - const char *title); -gpg_error_t b64enc_write (struct b64state *state, - const void *buffer, size_t nbytes); -gpg_error_t b64enc_finish (struct b64state *state); - -gpg_error_t b64dec_start (struct b64state *state, const char *title); -gpg_error_t b64dec_proc (struct b64state *state, void *buffer, size_t length, - size_t *r_nbytes); -gpg_error_t b64dec_finish (struct b64state *state); - /*-- sexputil.c */ char *canon_sexp_to_string (const unsigned char *canon, size_t canonlen); void log_printcanon (const char *text, const unsigned char *sexp, size_t sexplen); void log_printsexp (const char *text, gcry_sexp_t sexp); gpg_error_t make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, int secure, unsigned char **r_buffer, size_t *r_buflen); gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen, unsigned char *grip); int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b); int cmp_canon_sexp (const unsigned char *a, size_t alen, const unsigned char *b, size_t blen, int (*tcmp)(void *ctx, int depth, const unsigned char *aval, size_t avallen, const unsigned char *bval, size_t bvallen), void *tcmpctx); unsigned char *make_simple_sexp_from_hexstr (const char *line, size_t *nscanned); int hash_algo_from_sigval (const unsigned char *sigval); unsigned char *make_canon_sexp_from_rsa_pk (const void *m, size_t mlen, const void *e, size_t elen, size_t *r_len); gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char const **r_n, size_t *r_nlen, unsigned char const **r_e, size_t *r_elen); gpg_error_t get_ecc_q_from_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char const **r_q, size_t *r_qlen); gpg_error_t uncompress_ecc_q_in_canon_sexp (const unsigned char *keydata, size_t keydatalen, unsigned char **r_newkeydata, size_t *r_newkeydatalen); int get_pk_algo_from_key (gcry_sexp_t key); int get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen); char *pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid); const char *pubkey_algo_to_string (int algo); const char *hash_algo_to_string (int algo); const char *cipher_mode_to_string (int mode); /*-- convert.c --*/ int hex2bin (const char *string, void *buffer, size_t length); int hexcolon2bin (const char *string, void *buffer, size_t length); char *bin2hex (const void *buffer, size_t length, char *stringbuf); char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf); const char *hex2str (const char *hexstring, char *buffer, size_t bufsize, size_t *buflen); char *hex2str_alloc (const char *hexstring, size_t *r_count); unsigned int hex2fixedbuf (const char *hexstr, void *buffer, size_t bufsize); /*-- percent.c --*/ char *percent_plus_escape (const char *string); char *percent_data_escape (int plus, const char *prefix, const void *data, size_t datalen); char *percent_plus_unescape (const char *string, int nulrepl); char *percent_unescape (const char *string, int nulrepl); size_t percent_plus_unescape_inplace (char *string, int nulrepl); size_t percent_unescape_inplace (char *string, int nulrepl); /*-- openpgp-oid.c --*/ gpg_error_t openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi); char *openpgp_oidbuf_to_str (const unsigned char *buf, size_t len); char *openpgp_oid_to_str (gcry_mpi_t a); int openpgp_oidbuf_is_ed25519 (const void *buf, size_t len); int openpgp_oid_is_ed25519 (gcry_mpi_t a); int openpgp_oidbuf_is_cv25519 (const void *buf, size_t len); int openpgp_oid_is_cv25519 (gcry_mpi_t a); int openpgp_oid_is_cv448 (gcry_mpi_t a); int openpgp_oid_is_ed448 (gcry_mpi_t a); const char *openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo); const char *openpgp_oid_to_curve (const char *oid, int canon); const char *openpgp_oid_or_name_to_curve (const char *oidname, int canon); const char *openpgp_enum_curves (int *idxp); const char *openpgp_is_curve_supported (const char *name, int *r_algo, unsigned int *r_nbits); const char *get_keyalgo_string (enum gcry_pk_algos algo, unsigned int nbits, const char *curve); /*-- homedir.c --*/ const char *standard_homedir (void); void gnupg_set_homedir (const char *newdir); void gnupg_maybe_make_homedir (const char *fname, int quiet); const char *gnupg_homedir (void); int gnupg_default_homedir_p (void); const char *gnupg_daemon_rootdir (void); const char *gnupg_socketdir (void); const char *gnupg_sysconfdir (void); const char *gnupg_bindir (void); const char *gnupg_libexecdir (void); const char *gnupg_libdir (void); const char *gnupg_datadir (void); const char *gnupg_localedir (void); const char *gpg_agent_socket_name (void); const char *dirmngr_socket_name (void); const char *keyboxd_socket_name (void); char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info); /* All module names. We also include gpg and gpgsm for the sake for gpgconf. */ #define GNUPG_MODULE_NAME_AGENT 1 #define GNUPG_MODULE_NAME_PINENTRY 2 #define GNUPG_MODULE_NAME_SCDAEMON 3 #define GNUPG_MODULE_NAME_DIRMNGR 4 #define GNUPG_MODULE_NAME_PROTECT_TOOL 5 #define GNUPG_MODULE_NAME_CHECK_PATTERN 6 #define GNUPG_MODULE_NAME_GPGSM 7 #define GNUPG_MODULE_NAME_GPG 8 #define GNUPG_MODULE_NAME_CONNECT_AGENT 9 #define GNUPG_MODULE_NAME_GPGCONF 10 #define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11 #define GNUPG_MODULE_NAME_GPGV 12 #define GNUPG_MODULE_NAME_KEYBOXD 13 #define GNUPG_MODULE_NAME_TPM2DAEMON 14 #define GNUPG_MODULE_NAME_CARD 15 #define GNUPG_MODULE_NAME_GPGTAR 16 const char *gnupg_module_name (int which); void gnupg_module_name_flush_some (void); void gnupg_set_builddir (const char *newdir); /* A list of constants to identify protocols. This is used by tools * which need to distinguish between the different protocols * implemented by GnuPG. May be used as bit flags. */ #define GNUPG_PROTOCOL_OPENPGP 1 /* The one and only (gpg). */ #define GNUPG_PROTOCOL_CMS 2 /* The core of S/MIME (gpgsm) */ #define GNUPG_PROTOCOL_SSH_AGENT 4 /* Out ssh-agent implementation */ /*-- gpgrlhelp.c --*/ void gnupg_rl_initialize (void); /*-- helpfile.c --*/ char *gnupg_get_help_string (const char *key, int only_current_locale); /*-- localename.c --*/ const char *gnupg_messages_locale_name (void); /*-- miscellaneous.c --*/ /* This function is called at startup to tell libgcrypt to use our own logging subsystem. */ void setup_libgcrypt_logging (void); /* Print an out of core message and die. */ void xoutofcore (void); /* Wrapper aroung gpgrt_reallocarray. Uses the gpgrt alloc function * which redirects to the Libgcrypt versions via * init_common_subsystems. Thus this can be used interchangeable with * the other alloc functions. */ void *xreallocarray (void *a, size_t oldnmemb, size_t nmemb, size_t size); /* Same as estream_asprintf but die on memory failure. */ char *xasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); /* This is now an alias to estream_asprintf. */ char *xtryasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2); /* Replacement for gcry_cipher_algo_name. */ const char *gnupg_cipher_algo_name (int algo); void obsolete_option (const char *configname, unsigned int configlineno, const char *name); const char *print_fname_stdout (const char *s); const char *print_fname_stdin (const char *s); void print_utf8_buffer3 (estream_t fp, const void *p, size_t n, const char *delim); void print_utf8_buffer2 (estream_t fp, const void *p, size_t n, int delim); void print_utf8_buffer (estream_t fp, const void *p, size_t n); void print_utf8_string (estream_t stream, const char *p); void print_hexstring (FILE *fp, const void *buffer, size_t length, int reserved); char *try_make_printable_string (const void *p, size_t n, int delim); char *make_printable_string (const void *p, size_t n, int delim); char *decode_c_string (const char *src); int match_multistr (const char *multistr,const char *match); int gnupg_compare_version (const char *a, const char *b); struct debug_flags_s { unsigned int flag; const char *name; }; int parse_debug_flag (const char *string, unsigned int *debugvar, const struct debug_flags_s *flags); struct compatibility_flags_s { unsigned int flag; const char *name; const char *desc; }; int parse_compatibility_flags (const char *string, unsigned int *flagvar, const struct compatibility_flags_s *flags); /*-- Simple replacement functions. */ /* We use the gnupg_ttyname macro to be safe not to run into conflicts which an extisting but broken ttyname. */ #if !defined(HAVE_TTYNAME) || defined(HAVE_BROKEN_TTYNAME) # define gnupg_ttyname(n) _gnupg_ttyname ((n)) /* Systems without ttyname (W32) will merely return NULL. */ static inline char * _gnupg_ttyname (int fd) { (void)fd; return NULL; } #else /*HAVE_TTYNAME*/ # define gnupg_ttyname(n) ttyname ((n)) #endif /*HAVE_TTYNAME */ #define gnupg_isatty(a) isatty ((a)) /*-- Macros to replace ctype ones to avoid locale problems. --*/ #define spacep(p) (*(p) == ' ' || *(p) == '\t') #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define alphap(p) ((*(p) >= 'A' && *(p) <= 'Z') \ || (*(p) >= 'a' && *(p) <= 'z')) #define alnump(p) (alphap (p) || digitp (p)) #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) /* Note this isn't identical to a C locale isspace() without \f and \v, but works for the purposes used here. */ #define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t') /* The atoi macros assume that the buffer has only valid digits. */ #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) #define xtoi_4(p) ((xtoi_2(p) * 256) + xtoi_2((p)+2)) #endif /*GNUPG_COMMON_UTIL_H*/ diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c index 5b6b648e2..620edf788 100644 --- a/dirmngr/crlfetch.c +++ b/dirmngr/crlfetch.c @@ -1,597 +1,599 @@ /* crlfetch.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2006, 2007 g10 Code GmbH * * This file is part of DirMngr. * * DirMngr is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * DirMngr 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 "crlfetch.h" #include "dirmngr.h" #include "misc.h" #include "http.h" #include "ks-engine.h" /* For ks_http_fetch. */ #if USE_LDAP # include "ldap-wrapper.h" #endif /* For detecting armored CRLs received via HTTP (yes, such CRLS really exits, e.g. http://grid.fzk.de/ca/gridka-crl.pem at least in June 2008) we need a context in the reader callback. */ struct reader_cb_context_s { - estream_t fp; /* The stream used with the ksba reader. */ - int checked:1; /* PEM/binary detection ahs been done. */ - int is_pem:1; /* The file stream is PEM encoded. */ - struct b64state b64state; /* The state used for Base64 decoding. */ + estream_t fp; /* The stream used with the ksba reader. */ + unsigned int checked:1; /* PEM/binary detection ahs been done. */ + unsigned int is_pem:1; /* The file stream is PEM encoded. */ + gpgrt_b64state_t b64state; /* The state used for Base64 decoding. */ }; /* We need to associate a reader object with the reader callback context. This table is used for it. */ struct file_reader_map_s { ksba_reader_t reader; struct reader_cb_context_s *cb_ctx; }; #define MAX_FILE_READER 50 static struct file_reader_map_s file_reader_map[MAX_FILE_READER]; /* Associate FP with READER. If the table is full wait until another thread has removed an entry. */ static void register_file_reader (ksba_reader_t reader, struct reader_cb_context_s *cb_ctx) { int i; for (;;) { for (i=0; i < MAX_FILE_READER; i++) if (!file_reader_map[i].reader) { file_reader_map[i].reader = reader; file_reader_map[i].cb_ctx = cb_ctx; return; } log_info (_("reader to file mapping table full - waiting\n")); gnupg_sleep (2); } } /* Scan the table for an entry matching READER, remove that entry and return the associated file pointer. */ static struct reader_cb_context_s * get_file_reader (ksba_reader_t reader) { struct reader_cb_context_s *cb_ctx = NULL; int i; for (i=0; i < MAX_FILE_READER; i++) if (file_reader_map[i].reader == reader) { cb_ctx = file_reader_map[i].cb_ctx; file_reader_map[i].reader = NULL; file_reader_map[i].cb_ctx = NULL; break; } return cb_ctx; } static int my_es_read (void *opaque, char *buffer, size_t nbytes, size_t *nread) { struct reader_cb_context_s *cb_ctx = opaque; int result; result = es_read (cb_ctx->fp, buffer, nbytes, nread); if (result) return result; /* Fixme we should check whether the semantics of es_read are okay and well defined. I have some doubts. */ if (nbytes && !*nread && es_feof (cb_ctx->fp)) return gpg_error (GPG_ERR_EOF); if (!nread && es_ferror (cb_ctx->fp)) return gpg_error (GPG_ERR_EIO); if (!cb_ctx->checked && *nread) { int c = *(unsigned char *)buffer; cb_ctx->checked = 1; if ( ((c & 0xc0) >> 6) == 0 /* class: universal */ && (c & 0x1f) == 16 /* sequence */ && (c & 0x20) /* is constructed */ ) ; /* Binary data. */ else { cb_ctx->is_pem = 1; - b64dec_start (&cb_ctx->b64state, ""); + cb_ctx->b64state = gpgrt_b64dec_start (""); + if (!cb_ctx->b64state) + return gpg_error_from_syserror (); } } if (cb_ctx->is_pem && *nread) { size_t nread2; - if (b64dec_proc (&cb_ctx->b64state, buffer, *nread, &nread2)) + if (gpgrt_b64dec_proc (cb_ctx->b64state, buffer, *nread, &nread2)) { /* EOF from decoder. */ *nread = 0; result = gpg_error (GPG_ERR_EOF); } else *nread = nread2; } return result; } /* For now we do not support LDAP over Tor. */ static gpg_error_t no_crl_due_to_tor (ctrl_t ctrl) { gpg_error_t err = gpg_error (GPG_ERR_NOT_SUPPORTED); const char *text = _("CRL access not possible due to Tor mode"); log_error ("%s", text); dirmngr_status_printf (ctrl, "NOTE", "no_crl_due_to_tor %u %s", err, text); return gpg_error (GPG_ERR_NOT_SUPPORTED); } /* Fetch CRL from URL and return the entire CRL using new ksba reader object in READER. Note that this reader object should be closed only using ldap_close_reader. */ gpg_error_t crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader) { gpg_error_t err; parsed_uri_t uri; estream_t httpfp = NULL; *reader = NULL; if (!url) return gpg_error (GPG_ERR_INV_ARG); if (opt.verbose) log_info ("fetching CRL from '%s'\n", url); err = http_parse_uri (&uri, url, 0); http_release_parsed_uri (uri); if (!err) /* Yes, our HTTP code groks that. */ { if (opt.disable_http) { log_error (_("CRL access not possible due to disabled %s\n"), "HTTP"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else { /* Note that we also allow root certificates loaded from * "/etc/gnupg/trusted-certs/". We also do not consult the * CRL for the TLS connection - that may lead to a loop. * Due to cacert.org redirecting their https URL to http we * also allow such a downgrade. */ err = ks_http_fetch (ctrl, url, (KS_HTTP_FETCH_TRUST_CFG | KS_HTTP_FETCH_NO_CRL | KS_HTTP_FETCH_ALLOW_DOWNGRADE ), &httpfp); } if (err) log_error (_("error retrieving '%s': %s\n"), url, gpg_strerror (err)); else { struct reader_cb_context_s *cb_ctx; cb_ctx = xtrycalloc (1, sizeof *cb_ctx); if (!cb_ctx) err = gpg_error_from_syserror (); else if (!(err = ksba_reader_new (reader))) { cb_ctx->fp = httpfp; err = ksba_reader_set_cb (*reader, &my_es_read, cb_ctx); if (!err) { /* The ksba reader misses a user pointer thus we * need to come up with our own way of associating a * file pointer (or well the callback context) with * the reader. It is only required when closing the * reader thus there is no performance issue doing * it this way. FIXME: We now have a close * notification which might be used here. */ register_file_reader (*reader, cb_ctx); httpfp = NULL; } } if (err) { log_error (_("error initializing reader object: %s\n"), gpg_strerror (err)); ksba_reader_release (*reader); *reader = NULL; xfree (cb_ctx); } } } else /* Let the LDAP code parse other schemes. */ { if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else if (dirmngr_use_tor ()) { err = no_crl_due_to_tor (ctrl); } else { # if USE_LDAP err = url_fetch_ldap (ctrl, url, reader); # else /*!USE_LDAP*/ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); # endif /*!USE_LDAP*/ } } es_fclose (httpfp); return err; } /* Fetch CRL for ISSUER using a default server. Return the entire CRL as a newly opened stream returned in R_FP. */ gpg_error_t crl_fetch_default (ctrl_t ctrl, const char *issuer, ksba_reader_t *reader) { if (dirmngr_use_tor ()) { return no_crl_due_to_tor (ctrl); } if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } #if USE_LDAP return attr_fetch_ldap (ctrl, issuer, "certificateRevocationList", reader); #else (void)ctrl; (void)issuer; (void)reader; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } /* Fetch a CA certificate for DN using the default server. This * function only initiates the fetch; fetch_next_cert must be used to * actually read the certificate; end_cert_fetch to end the * operation. */ gpg_error_t ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, const char *dn) { if (dirmngr_use_tor ()) { return no_crl_due_to_tor (ctrl); } if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } #if USE_LDAP return start_cacert_fetch_ldap (ctrl, context, dn); #else (void)ctrl; (void)context; (void)dn; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } gpg_error_t start_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, strlist_t patterns, const ldap_server_t server) { if (dirmngr_use_tor ()) { return no_crl_due_to_tor (ctrl); } if (opt.disable_ldap) { log_error (_("certificate search not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } #if USE_LDAP return start_cert_fetch_ldap (ctrl, context, patterns, server); #else (void)ctrl; (void)context; (void)patterns; (void)server; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } gpg_error_t fetch_next_cert (cert_fetch_context_t context, unsigned char **value, size_t * valuelen) { #if USE_LDAP return fetch_next_cert_ldap (context, value, valuelen); #else (void)context; (void)value; (void)valuelen; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } /* Fetch the next data from CONTEXT, assuming it is a certificate and return * it as a cert object in R_CERT. */ gpg_error_t fetch_next_ksba_cert (cert_fetch_context_t context, ksba_cert_t *r_cert) { gpg_error_t err; unsigned char *value = NULL; size_t valuelen = 0; ksba_cert_t cert; *r_cert = NULL; #if USE_LDAP err = fetch_next_cert_ldap (context, &value, &valuelen); if (!err && !value) err = gpg_error (GPG_ERR_BUG); #else (void)context; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif if (err) return err; err = ksba_cert_new (&cert); if (err) { xfree (value); return err; } err = ksba_cert_init_from_mem (cert, value, valuelen); xfree (value); if (err) { ksba_cert_release (cert); return err; } *r_cert = cert; return 0; } void end_cert_fetch (cert_fetch_context_t context) { #if USE_LDAP end_cert_fetch_ldap (context); #else (void)context; #endif } /* Read a certificate from an HTTP URL and return it as an estream * memory buffer at R_FP. */ static gpg_error_t read_cert_via_http (ctrl_t ctrl, const char *url, estream_t *r_fp) { gpg_error_t err; estream_t fp = NULL; estream_t httpfp = NULL; size_t nread, nwritten; char buffer[1024]; if ((err = ks_http_fetch (ctrl, url, KS_HTTP_FETCH_TRUST_CFG, &httpfp))) goto leave; /* We now read the data from the web server into a memory buffer. * To DOS we limit the certificate length to 32k. */ fp = es_fopenmem (32*1024, "rw"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } for (;;) { if (es_read (httpfp, buffer, sizeof buffer, &nread)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", es_fname_get (httpfp), gpg_strerror (err)); goto leave; } if (!nread) break; /* Ready. */ if (es_write (fp, buffer, nread, &nwritten)) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", es_fname_get (fp), gpg_strerror (err)); goto leave; } else if (nread != nwritten) { err = gpg_error (GPG_ERR_EIO); log_error ("error writing '%s': %s\n", es_fname_get (fp), "short write"); goto leave; } } es_rewind (fp); *r_fp = fp; fp = NULL; leave: es_fclose (httpfp); es_fclose (fp); return err; } /* Lookup a cert by it's URL. */ gpg_error_t fetch_cert_by_url (ctrl_t ctrl, const char *url, unsigned char **value, size_t *valuelen) { const unsigned char *cert_image = NULL; size_t cert_image_n; ksba_reader_t reader = NULL; ksba_cert_t cert = NULL; gpg_error_t err; *value = NULL; *valuelen = 0; err = ksba_cert_new (&cert); if (err) goto leave; if (url && (!strncmp (url, "http:", 5) || !strncmp (url, "https:", 6))) { estream_t stream; void *der; size_t derlen; err = read_cert_via_http (ctrl, url, &stream); if (err) goto leave; if (es_fclose_snatch (stream, &der, &derlen)) { err = gpg_error_from_syserror (); goto leave; } err = ksba_cert_init_from_mem (cert, der, derlen); xfree (der); if (err) goto leave; } else /* Assume LDAP. */ { #if USE_LDAP err = url_fetch_ldap (ctrl, url, &reader); #else (void)ctrl; (void)url; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif /*USE_LDAP*/ if (err) goto leave; err = ksba_cert_read_der (cert, reader); if (err) goto leave; } cert_image = ksba_cert_get_image (cert, &cert_image_n); if (!cert_image || !cert_image_n) { err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto leave; } *value = xtrymalloc (cert_image_n); if (!*value) { err = gpg_error_from_syserror (); goto leave; } memcpy (*value, cert_image, cert_image_n); *valuelen = cert_image_n; leave: ksba_cert_release (cert); #if USE_LDAP ldap_wrapper_release_context (reader); #endif /*USE_LDAP*/ return err; } /* This function is to be used to close the reader object. In addition to running ksba_reader_release it also releases the LDAP or HTTP contexts associated with that reader. */ void crl_close_reader (ksba_reader_t reader) { struct reader_cb_context_s *cb_ctx; if (!reader) return; /* Check whether this is a HTTP one. */ cb_ctx = get_file_reader (reader); if (cb_ctx) { /* This is an HTTP context. */ if (cb_ctx->fp) es_fclose (cb_ctx->fp); /* Release the base64 decoder state. */ if (cb_ctx->is_pem) - b64dec_finish (&cb_ctx->b64state); + gpgrt_b64dec_finish (cb_ctx->b64state); /* Release the callback context. */ xfree (cb_ctx); } else /* This is an ldap wrapper context (Currently not used). */ { #if USE_LDAP ldap_wrapper_release_context (reader); #endif /*USE_LDAP*/ } /* Now get rid of the reader object. */ ksba_reader_release (reader); } diff --git a/dirmngr/dirmngr-client.c b/dirmngr/dirmngr-client.c index f1059c939..ece4fbcc9 100644 --- a/dirmngr/dirmngr-client.c +++ b/dirmngr/dirmngr-client.c @@ -1,945 +1,945 @@ /* dirmngr-client.c - A client for the dirmngr daemon * Copyright (C) 2004, 2007 g10 Code GmbH * Copyright (C) 2002, 2003 Free Software Foundation, Inc. * * This file is part of DirMngr. * * DirMngr is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * DirMngr 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include #include #include "../common/logging.h" #include "../common/stringhelp.h" #include "../common/mischelp.h" #include "../common/strlist.h" #include "../common/asshelp.h" #include "../common/i18n.h" #include "../common/util.h" #include "../common/init.h" /* Constants for the options. */ enum { oQuiet = 'q', oVerbose = 'v', oLocal = 'l', oUrl = 'u', oOCSP = 500, oPing, oCacheCert, oValidate, oLookup, oLoadCRL, oSquidMode, oPEM, oEscapedPEM, oForceDefaultResponder }; /* The list of options as used by the argparse.c code. */ static gpgrt_opt_t opts[] = { { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, { oOCSP, "ocsp", 0, N_("use OCSP instead of CRLs") }, { oPing, "ping", 0, N_("check whether a dirmngr is running")}, { oCacheCert,"cache-cert",0, N_("add a certificate to the cache")}, { oValidate, "validate", 0, N_("validate a certificate")}, { oLookup, "lookup", 0, N_("lookup a certificate")}, { oLocal, "local", 0, N_("lookup only locally stored certificates")}, { oUrl, "url", 0, N_("expect an URL for --lookup")}, { oLoadCRL, "load-crl", 0, N_("load a CRL into the dirmngr")}, { oSquidMode,"squid-mode",0, N_("special mode for use by Squid")}, { oPEM, "pem", 0, N_("expect certificates in PEM format")}, { oForceDefaultResponder, "force-default-responder", 0, N_("force the use of the default OCSP responder")}, ARGPARSE_end () }; /* The usual structure for the program flags. */ static struct { int quiet; int verbose; const char *dirmngr_program; int force_default_responder; int pem; int escaped_pem; /* PEM is additional percent encoded. */ int url; /* Expect an URL. */ int local; /* Lookup up only local certificates. */ int use_ocsp; } opt; /* Communication structure for the certificate inquire callback. */ struct inq_cert_parm_s { assuan_context_t ctx; const unsigned char *cert; size_t certlen; }; /* Base64 conversion tables. */ static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; static unsigned char asctobin[256]; /* runtime initialized */ /* Build the helptable for radix64 to bin conversion. */ static void init_asctobin (void) { static int initialized; int i; unsigned char *s; if (initialized) return; initialized = 1; for (i=0; i < 256; i++ ) asctobin[i] = 255; /* Used to detect invalid characters. */ for (s=bintoasc, i=0; *s; s++, i++) asctobin[*s] = i; } /* Prototypes. */ static gpg_error_t read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen); static gpg_error_t do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen); static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename); static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern); static gpg_error_t squid_loop_body (assuan_context_t ctx); /* Function called by argparse.c to display information. */ static const char * my_strusage (int level) { const char *p; switch(level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "dirmngr-client (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 49: p = PACKAGE_BUGREPORT; break; case 1: case 40: p = _("Usage: dirmngr-client [options] " "[certfile|pattern] (-h for help)\n"); break; case 41: p = _("Syntax: dirmngr-client [options] [certfile|pattern]\n" "Test an X.509 certificate against a CRL or do an OCSP check\n" "The process returns 0 if the certificate is valid, 1 if it is\n" "not valid and other error codes for general failures\n"); break; default: p = NULL; } return p; } int main (int argc, char **argv ) { gpgrt_argparse_t pargs; assuan_context_t ctx; gpg_error_t err; unsigned char *certbuf; size_t certbuflen = 0; int cmd_ping = 0; int cmd_cache_cert = 0; int cmd_validate = 0; int cmd_lookup = 0; int cmd_loadcrl = 0; int cmd_squid_mode = 0; early_system_init (); gpgrt_set_strusage (my_strusage); log_set_prefix ("dirmngr-client", GPGRT_LOG_WITH_PREFIX); /* Register our string mapper with gpgrt. Usually done in * init_common_subsystems, but we don't need that here. */ gpgrt_set_fixed_string_mapper (map_static_macro_string); /* For W32 we need to initialize the socket subsystem. Because we don't use Pth we need to do this explicit. */ #ifdef HAVE_W32_SYSTEM { WSADATA wsadat; WSAStartup (0x202, &wsadat); } #endif /*HAVE_W32_SYSTEM*/ /* Init Assuan. */ assuan_set_assuan_log_prefix (log_get_prefix (NULL)); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); /* Setup I18N. */ i18n_init(); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags= ARGPARSE_FLAG_KEEP; while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oVerbose: opt.verbose++; break; case oQuiet: opt.quiet++; break; case oOCSP: opt.use_ocsp++; break; case oPing: cmd_ping = 1; break; case oCacheCert: cmd_cache_cert = 1; break; case oValidate: cmd_validate = 1; break; case oLookup: cmd_lookup = 1; break; case oUrl: opt.url = 1; break; case oLocal: opt.local = 1; break; case oLoadCRL: cmd_loadcrl = 1; break; case oPEM: opt.pem = 1; break; case oSquidMode: opt.pem = 1; opt.escaped_pem = 1; cmd_squid_mode = 1; break; case oForceDefaultResponder: opt.force_default_responder = 1; break; default : pargs.err = ARGPARSE_PRINT_ERROR; break; } } gpgrt_argparse (NULL, &pargs, NULL); if (log_get_errorcount (0)) exit (2); if (cmd_ping) err = 0; else if (cmd_lookup || cmd_loadcrl) { if (!argc) gpgrt_usage (1); err = 0; } else if (cmd_squid_mode) { err = 0; if (argc) gpgrt_usage (1); } else if (!argc) { err = read_certificate (NULL, &certbuf, &certbuflen); if (err) log_error (_("error reading certificate from stdin: %s\n"), gpg_strerror (err)); } else if (argc == 1) { err = read_certificate (*argv, &certbuf, &certbuflen); if (err) log_error (_("error reading certificate from '%s': %s\n"), *argv, gpg_strerror (err)); } else { err = 0; gpgrt_usage (1); } if (log_get_errorcount (0)) exit (2); if (certbuflen > 20000) { log_error (_("certificate too large to make any sense\n")); exit (2); } err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.dirmngr_program ? opt.dirmngr_program : gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR), cmd_ping? 0 : ASSHELP_FLAG_AUTOSTART, opt.verbose, 0, NULL, NULL); if (err) { log_error (_("can't connect to the dirmngr: %s\n"), gpg_strerror (err)); exit (2); } if (cmd_ping) ; else if (cmd_squid_mode) { while (!(err = squid_loop_body (ctx))) ; if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; } else if (cmd_lookup) { int last_err = 0; for (; argc; argc--, argv++) { err = do_lookup (ctx, *argv); if (err) { log_error (_("lookup failed: %s\n"), gpg_strerror (err)); last_err = err; } } err = last_err; } else if (cmd_loadcrl) { int last_err = 0; for (; argc; argc--, argv++) { err = do_loadcrl (ctx, *argv); if (err) { log_error (_("loading CRL '%s' failed: %s\n"), *argv, gpg_strerror (err)); last_err = err; } } err = last_err; } else if (cmd_cache_cert) { err = do_cache (ctx, certbuf, certbuflen); xfree (certbuf); } else if (cmd_validate) { err = do_validate (ctx, certbuf, certbuflen); xfree (certbuf); } else { err = do_check (ctx, certbuf, certbuflen); xfree (certbuf); } assuan_release (ctx); if (cmd_ping) { if (!opt.quiet) log_info (_("a dirmngr daemon is up and running\n")); return 0; } else if (cmd_lookup|| cmd_loadcrl || cmd_squid_mode) return err? 1:0; else if (cmd_cache_cert) { if (err && gpg_err_code (err) == GPG_ERR_DUP_VALUE ) { if (!opt.quiet) log_info (_("certificate already cached\n")); } else if (err) { log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); return 1; } return 0; } else if (cmd_validate && err) { log_error (_("validation of certificate failed: %s\n"), gpg_strerror (err)); return 1; } else if (!err) { if (!opt.quiet) log_info (_("certificate is valid\n")); return 0; } else if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) { if (!opt.quiet) log_info (_("certificate has been revoked\n")); return 1; } else { log_error (_("certificate check failed: %s\n"), gpg_strerror (err)); return 2; } } /* Print status line from the assuan protocol. */ static gpg_error_t status_cb (void *opaque, const char *line) { (void)opaque; if (opt.verbose > 2) log_info (_("got status: '%s'\n"), line); return 0; } /* Print data as retrieved by the lookup function. */ static gpg_error_t data_cb (void *opaque, const void *buffer, size_t length) { gpg_error_t err; - struct b64state *state = opaque; + gpgrt_b64state_t state = opaque; if (buffer) { - err = b64enc_write (state, buffer, length); + err = gpgrt_b64enc_write (state, buffer, length); if (err) log_error (_("error writing base64 encoding: %s\n"), gpg_strerror (err)); } return 0; } /* Read the first PEM certificate from the file FNAME. If fname is NULL the next certificate is read from stdin. The certificate is returned in an alloced buffer whose address will be returned in RBUF and its length in RBUFLEN. */ static gpg_error_t read_pem_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen, int no_errmsg) { estream_t fp; int c; int pos; int value; unsigned char *buf; size_t bufsize, buflen; enum { s_init, s_idle, s_lfseen, s_begin, s_b64_0, s_b64_1, s_b64_2, s_b64_3, s_waitend } state = s_init; init_asctobin (); fp = fname? es_fopen (fname, "r") : es_stdin; if (!fp) return gpg_error_from_syserror (); pos = 0; value = 0; bufsize = 8192; buf = xmalloc (bufsize); buflen = 0; while ((c=es_getc (fp)) != EOF) { int escaped_c = 0; if (opt.escaped_pem) { if (c == '%') { char tmp[2]; if ((c = es_getc(fp)) == EOF) break; tmp[0] = c; if ((c = es_getc(fp)) == EOF) break; tmp[1] = c; if (!hexdigitp (tmp) || !hexdigitp (tmp+1)) { log_error ("invalid percent escape sequence\n"); state = s_idle; /* Force an error. */ /* Skip to end of line. */ while ( (c=es_getc (fp)) != EOF && c != '\n') ; goto ready; } c = xtoi_2 (tmp); escaped_c = 1; } else if (c == '\n') goto ready; /* Ready. */ } switch (state) { case s_idle: if (c == '\n') { state = s_lfseen; pos = 0; } break; case s_init: state = s_lfseen; /* fall through */ case s_lfseen: if (c != "-----BEGIN "[pos]) state = s_idle; else if (pos == 10) state = s_begin; else pos++; break; case s_begin: if (c == '\n') state = s_b64_0; break; case s_b64_0: case s_b64_1: case s_b64_2: case s_b64_3: { if (buflen >= bufsize) { bufsize += 8192; buf = xrealloc (buf, bufsize); } if (c == '-') state = s_waitend; else if ((c = asctobin[c & 0xff]) == 255 ) ; /* Just skip invalid base64 characters. */ else if (state == s_b64_0) { value = c << 2; state = s_b64_1; } else if (state == s_b64_1) { value |= (c>>4)&3; buf[buflen++] = value; value = (c<<4)&0xf0; state = s_b64_2; } else if (state == s_b64_2) { value |= (c>>2)&15; buf[buflen++] = value; value = (c<<6)&0xc0; state = s_b64_3; } else { value |= c&0x3f; buf[buflen++] = value; state = s_b64_0; } } break; case s_waitend: /* Note that we do not check that the base64 decoder has been left in the expected state. We assume that the PEM header is just fine. However we need to wait for the real LF and not a trailing percent escaped one. */ if (c== '\n' && !escaped_c) goto ready; break; default: BUG(); } } ready: if (fname) es_fclose (fp); if (state == s_init && c == EOF) { xfree (buf); return gpg_error (GPG_ERR_EOF); } else if (state != s_waitend) { if (!no_errmsg) log_error ("no certificate or invalid encoded\n"); xfree (buf); return gpg_error (GPG_ERR_INV_ARMOR); } *rbuf = buf; *rbuflen = buflen; return 0; } /* Read a binary certificate from the file FNAME. If fname is NULL the file is read from stdin. The certificate is returned in an alloced buffer whose address will be returned in RBUF and its length in RBUFLEN. */ static gpg_error_t read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen) { gpg_error_t err; estream_t fp; unsigned char *buf; size_t nread, bufsize, buflen; if (opt.pem) return read_pem_certificate (fname, rbuf, rbuflen, 0); else if (fname) { /* A filename has been given. Let's just assume it is in PEM format and decode it, and fall back to interpreting it as binary certificate if that fails. */ err = read_pem_certificate (fname, rbuf, rbuflen, 1); if (! err) return 0; /* Clear the error count to try as binary certificate. */ log_get_errorcount (1); } fp = fname? es_fopen (fname, "rb") : es_stdin; if (!fp) return gpg_error_from_syserror (); buf = NULL; bufsize = buflen = 0; #define NCHUNK 8192 do { bufsize += NCHUNK; if (!buf) buf = xmalloc (bufsize); else buf = xrealloc (buf, bufsize); nread = es_fread (buf+buflen, 1, NCHUNK, fp); if (nread < NCHUNK && es_ferror (fp)) { err = gpg_error_from_syserror (); xfree (buf); if (fname) es_fclose (fp); return err; } buflen += nread; } while (nread == NCHUNK); #undef NCHUNK if (fname) es_fclose (fp); *rbuf = buf; *rbuflen = buflen; return 0; } /* Callback for the inquire fiunction to send back the certificate. */ static gpg_error_t inq_cert (void *opaque, const char *line) { struct inq_cert_parm_s *parm = opaque; gpg_error_t err; if (!strncmp (line, "TARGETCERT", 10) && (line[10] == ' ' || !line[10])) { err = assuan_send_data (parm->ctx, parm->cert, parm->certlen); } else if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back and empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else if (!strncmp (line, "SENDCERT_SKI", 12) && (line[12]==' ' || !line[12])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back an empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else if (!strncmp (line, "SENDISSUERCERT", 14) && (line[14] == ' ' || !line[14])) { /* We don't support this but dirmngr might ask for it. So simply ignore it by sending back an empty value. */ err = assuan_send_data (parm->ctx, NULL, 0); } else { log_info (_("unsupported inquiry '%s'\n"), line); err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); /* Note that this error will let assuan_transact terminate immediately instead of return the error to the caller. It is not clear whether this is the desired behaviour - it may change in future. */ } return err; } /* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. Return a proper error code. */ static gpg_error_t do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, (opt.use_ocsp && opt.force_default_responder ? "CHECKOCSP --force-default-responder" : opt.use_ocsp? "CHECKOCSP" : "CHECKCRL"), NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. Return a proper error code. */ static gpg_error_t do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, "CACHECERT", NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Check the certificate CERT,CERTLEN for validity using dirmngrs internal validate feature. Return a proper error code. */ static gpg_error_t do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen) { gpg_error_t err; struct inq_cert_parm_s parm; memset (&parm, 0, sizeof parm); parm.ctx = ctx; parm.cert = cert; parm.certlen = certlen; err = assuan_transact (ctx, "VALIDATE", NULL, NULL, inq_cert, &parm, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); return err; } /* Load a CRL into the dirmngr. */ static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename) { gpg_error_t err; const char *s; char *fname, *line, *p; if (opt.url) fname = xstrdup (filename); else { #ifdef HAVE_CANONICALIZE_FILE_NAME fname = canonicalize_file_name (filename); if (!fname) { log_error ("error canonicalizing '%s': %s\n", filename, strerror (errno)); return gpg_error (GPG_ERR_GENERAL); } #else fname = xstrdup (filename); #endif if (*fname != '/') { log_error (_("absolute file name expected\n")); return gpg_error (GPG_ERR_GENERAL); } } line = xmalloc (8 + 6 + strlen (fname) * 3 + 1); p = stpcpy (line, "LOADCRL "); if (opt.url) p = stpcpy (p, "--url "); for (s = fname; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); xfree (line); xfree (fname); return err; } /* Do a LDAP lookup using PATTERN and print the result in a base-64 encoded format. */ static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern) { gpg_error_t err; const unsigned char *s; char *line, *p; - struct b64state state; + gpgrt_b64state_t state; if (opt.verbose) log_info (_("looking up '%s'\n"), pattern); - err = b64enc_start (&state, stdout, NULL); - if (err) - return err; + state = gpgrt_b64enc_start (es_stdout, NULL); + if (!state) + return gpg_error_from_syserror (); line = xmalloc (10 + 6 + 13 + strlen (pattern)*3 + 1); p = stpcpy (line, "LOOKUP "); if (opt.url) p = stpcpy (p, "--url "); if (opt.local) p = stpcpy (p, "--cache-only "); for (s=pattern; *s; s++) { if (*s < ' ' || *s == '+') { sprintf (p, "%%%02X", *s); p += 3; } else if (*s == ' ') *p++ = '+'; else *p++ = *s; } *p = 0; err = assuan_transact (ctx, line, - data_cb, &state, + data_cb, state, NULL, NULL, status_cb, NULL); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); - err = b64enc_finish (&state); + err = gpgrt_b64enc_finish (state); xfree (line); return err; } /* The body of an endless loop: Read a line from stdin, retrieve the certificate from it, validate it and print "ERR" or "OK" to stdout. Continue. */ static gpg_error_t squid_loop_body (assuan_context_t ctx) { gpg_error_t err; unsigned char *certbuf; size_t certbuflen = 0; err = read_pem_certificate (NULL, &certbuf, &certbuflen, 0); if (gpg_err_code (err) == GPG_ERR_EOF) return err; if (err) { log_error (_("error reading certificate from stdin: %s\n"), gpg_strerror (err)); puts ("ERROR"); return 0; } err = do_check (ctx, certbuf, certbuflen); xfree (certbuf); if (!err) { if (opt.verbose) log_info (_("certificate is valid\n")); puts ("OK"); } else { if (!opt.quiet) { if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) log_info (_("certificate has been revoked\n")); else log_error (_("certificate check failed: %s\n"), gpg_strerror (err)); } puts ("ERROR"); } fflush (stdout); return 0; } diff --git a/dirmngr/misc.c b/dirmngr/misc.c index 9cedf911c..d1830237d 100644 --- a/dirmngr/misc.c +++ b/dirmngr/misc.c @@ -1,656 +1,662 @@ /* misc.c - miscellaneous * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2002, 2003, 2004, 2010 Free Software Foundation, Inc. * * This file is part of DirMngr. * * DirMngr is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * DirMngr is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include #include "dirmngr.h" #include "../common/util.h" #include "misc.h" /* Convert the hex encoded STRING back into binary and store the result into the provided buffer RESULT. The actual size of that buffer will be returned. The caller should provide RESULT of at least strlen(STRING)/2 bytes. There is no error detection, the parsing stops at the first non hex character. With RESULT given as NULL, the function does only return the size of the buffer which would be needed. */ size_t unhexify (unsigned char *result, const char *string) { const char *s; size_t n; for (s=string,n=0; hexdigitp (s) && hexdigitp(s+1); s += 2) { if (result) result[n] = xtoi_2 (s); n++; } return n; } char* hashify_data( const char* data, size_t len ) { unsigned char buf[20]; gcry_md_hash_buffer (GCRY_MD_SHA1, buf, data, len); return hexify_data (buf, 20, 0); } /* FIXME: Replace this by hextobin. */ char* hexify_data (const unsigned char* data, size_t len, int with_prefix) { int i; char *result = xmalloc (2*len + (with_prefix?2:0) + 1); char *p; if (with_prefix) p = stpcpy (result, "0x"); else p = result; for (i = 0; i < 2*len; i+=2 ) snprintf (p+i, 3, "%02X", *data++); return result; } char * serial_hex (ksba_sexp_t serial ) { unsigned char* p = serial; char *endp; unsigned long n; char *certid; if (!p) return NULL; else { p++; /* ignore initial '(' */ n = strtoul (p, (char**)&endp, 10); p = endp; if (*p!=':') return NULL; else { int i = 0; certid = xmalloc( sizeof( char )*(2*n + 1 ) ); for (p++; n; n--, p++) { sprintf ( certid+i , "%02X", *p); i += 2; } } } return certid; } /* Take an S-Expression encoded blob and return a pointer to the actual data as well as its length. Return NULL for an invalid S-Expression.*/ const unsigned char * serial_to_buffer (const ksba_sexp_t serial, size_t *length) { unsigned char *p = serial; char *endp; unsigned long n; if (!p || *p != '(') return NULL; p++; n = strtoul (p, &endp, 10); p = endp; if (*p != ':') return NULL; p++; *length = n; return p; } /* Do an in-place percent unescaping of STRING. Returns STRING. Note that this function does not do a '+'-to-space unescaping.*/ char * unpercent_string (char *string) { char *s = string; char *d = string; while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 ( s); s += 2; } else *d++ = *s++; } *d = 0; return string; } /* Convert a canonical encoded S-expression in CANON into the GCRY type. */ gpg_error_t canon_sexp_to_gcry (const unsigned char *canon, gcry_sexp_t *r_sexp) { gpg_error_t err; size_t n; gcry_sexp_t sexp; *r_sexp = NULL; n = gcry_sexp_canon_len (canon, 0, NULL, NULL); if (!n) { log_error (_("invalid canonical S-expression found\n")); err = gpg_error (GPG_ERR_INV_SEXP); } else if ((err = gcry_sexp_sscan (&sexp, NULL, canon, n))) log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err)); else *r_sexp = sexp; return err; } /* Return an allocated buffer with the formatted fingerprint as one large hexnumber */ char * get_fingerprint_hexstring (ksba_cert_t cert) { unsigned char digest[20]; gcry_md_hd_t md; int rc; char *buf; int i; rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (rc) log_fatal (_("gcry_md_open failed: %s\n"), gpg_strerror (rc)); rc = ksba_cert_hash (cert, 0, HASH_FNC, md); if (rc) { log_error (_("oops: ksba_cert_hash failed: %s\n"), gpg_strerror (rc)); memset (digest, 0xff, 20); /* Use a dummy value. */ } else { gcry_md_final (md); memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20); } gcry_md_close (md); buf = xmalloc (41); *buf = 0; for (i=0; i < 20; i++ ) sprintf (buf+strlen(buf), "%02X", digest[i]); return buf; } /* Return an allocated buffer with the formatted fingerprint as one large hexnumber. This version inserts the usual colons. */ char * get_fingerprint_hexstring_colon (ksba_cert_t cert) { unsigned char digest[20]; gcry_md_hd_t md; int rc; char *buf; int i; rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (rc) log_fatal (_("gcry_md_open failed: %s\n"), gpg_strerror (rc)); rc = ksba_cert_hash (cert, 0, HASH_FNC, md); if (rc) { log_error (_("oops: ksba_cert_hash failed: %s\n"), gpg_strerror (rc)); memset (digest, 0xff, 20); /* Use a dummy value. */ } else { gcry_md_final (md); memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20); } gcry_md_close (md); buf = xmalloc (61); *buf = 0; for (i=0; i < 20; i++ ) sprintf (buf+strlen(buf), "%02X:", digest[i]); buf[strlen(buf)-1] = 0; /* Remove railing colon. */ return buf; } /* Dump the serial number SERIALNO to the log stream. */ void dump_serial (ksba_sexp_t serialno) { char *p; p = serial_hex (serialno); log_printf ("%s", p?p:"?"); xfree (p); } /* Dump STRING to the log file but choose the best readable format. */ void dump_string (const char *string) { if (!string) log_printf ("[error]"); else { const unsigned char *s; for (s=string; *s; s++) { if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0)) break; } if (!*s && *string != '[') log_printf ("%s", string); else { log_printf ( "[ "); log_printhex (string, strlen (string), NULL); log_printf ( " ]"); } } } /* Dump an KSBA cert object to the log stream. Prefix the output with TEXT. This is used for debugging. */ void dump_cert (const char *text, ksba_cert_t cert) { ksba_sexp_t sexp; char *p; ksba_isotime_t t; int idx; log_debug ("BEGIN Certificate '%s':\n", text? text:""); if (cert) { sexp = ksba_cert_get_serial (cert); p = serial_hex (sexp); log_debug (" serial: %s\n", p?p:"?"); xfree (p); ksba_free (sexp); ksba_cert_get_validity (cert, 0, t); log_debug (" notBefore: "); dump_isotime (t); log_printf ("\n"); ksba_cert_get_validity (cert, 1, t); log_debug (" notAfter: "); dump_isotime (t); log_printf ("\n"); p = ksba_cert_get_issuer (cert, 0); log_debug (" issuer: "); dump_string (p); ksba_free (p); log_printf ("\n"); p = ksba_cert_get_subject (cert, 0); log_debug (" subject: "); dump_string (p); ksba_free (p); log_printf ("\n"); for (idx=1; (p = ksba_cert_get_subject (cert, idx)); idx++) { log_debug (" aka: "); dump_string (p); ksba_free (p); log_printf ("\n"); } log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert)); p = get_fingerprint_hexstring (cert); log_debug (" SHA1 fingerprint: %s\n", p); xfree (p); } log_debug ("END Certificate\n"); } /* Log the certificate's name in "#SN/ISSUERDN" format along with TEXT. */ void cert_log_name (const char *text, ksba_cert_t cert) { log_info ("%s", text? text:"certificate" ); if (cert) { ksba_sexp_t sn; char *p; p = ksba_cert_get_issuer (cert, 0); sn = ksba_cert_get_serial (cert); if (p && sn) { log_printf (" #"); dump_serial (sn); log_printf ("/"); dump_string (p); } else log_printf (" [invalid]"); ksba_free (sn); xfree (p); } log_printf ("\n"); } /* Log the certificate's subject DN along with TEXT. */ void cert_log_subject (const char *text, ksba_cert_t cert) { log_info ("%s", text? text:"subject" ); if (cert) { char *p; p = ksba_cert_get_subject (cert, 0); if (p) { log_printf (" /"); dump_string (p); xfree (p); } else log_printf (" [invalid]"); } log_printf ("\n"); } /* Callback to print infos about the TLS certificates. */ void cert_log_cb (http_session_t sess, gpg_error_t err, const char *hostname, const void **certs, size_t *certlens) { ksba_cert_t cert; size_t n; (void)sess; if (!err) return; /* No error - no need to log anything */ log_debug ("expected hostname: %s\n", hostname); for (n=0; certs[n]; n++) { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, certs[n], certlens[n]); if (err) log_error ("error parsing cert for logging: %s\n", gpg_strerror (err)); else { char textbuf[20]; snprintf (textbuf, sizeof textbuf, "server[%u]", (unsigned int)n); dump_cert (textbuf, cert); } ksba_cert_release (cert); } } /**************** * Remove all %xx escapes; this is done inplace. * Returns: New length of the string. */ static int remove_percent_escapes (unsigned char *string) { int n = 0; unsigned char *p, *s; for (p = s = string; *s; s++) { if (*s == '%') { if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2)) { s++; *p = xtoi_2 (s); s++; p++; n++; } else { *p++ = *s++; if (*s) *p++ = *s++; if (*s) *p++ = *s++; if (*s) *p = 0; return -1; /* Bad URI. */ } } else { *p++ = *s; n++; } } *p = 0; /* Always keep a string terminator. */ return n; } /* Return the host name and the port (0 if none was given) from the URL. Return NULL on error or if host is not included in the URL. */ char * host_and_port_from_url (const char *url, int *port) { const char *s, *s2; char *buf, *p; int n; s = url; *port = 0; /* Find the scheme */ if ( !(s2 = strchr (s, ':')) || s2 == s ) return NULL; /* No scheme given. */ s = s2+1; /* Find the hostname */ if (*s != '/') return NULL; /* Does not start with a slash. */ s++; if (*s != '/') return NULL; /* No host name. */ s++; buf = xtrystrdup (s); if (!buf) { log_error (_("malloc failed: %s\n"), strerror (errno)); return NULL; } if ((p = strchr (buf, '/'))) *p++ = 0; strlwr (buf); if ((p = strchr (buf, ':'))) { *p++ = 0; *port = atoi (p); } /* Remove quotes and make sure that no Nul has been encoded. */ if ((n = remove_percent_escapes (buf)) < 0 || n != strlen (buf) ) { log_error (_("bad URL encoding detected\n")); xfree (buf); return NULL; } return buf; } /* A KSBA reader callback to read from an estream. */ static int my_estream_ksba_reader_cb (void *cb_value, char *buffer, size_t count, size_t *r_nread) { estream_t fp = cb_value; if (!fp) return gpg_error (GPG_ERR_INV_VALUE); if (!buffer && !count && !r_nread) { es_rewind (fp); return 0; } *r_nread = es_fread (buffer, 1, count, fp); if (!*r_nread) return -1; /* EOF or error. */ return 0; /* Success. */ } /* Create a KSBA reader object and connect it to the estream FP. */ gpg_error_t create_estream_ksba_reader (ksba_reader_t *r_reader, estream_t fp) { gpg_error_t err; ksba_reader_t reader; *r_reader = NULL; err = ksba_reader_new (&reader); if (!err) err = ksba_reader_set_cb (reader, my_estream_ksba_reader_cb, fp); if (err) { log_error (_("error initializing reader object: %s\n"), gpg_strerror (err)); ksba_reader_release (reader); return err; } *r_reader = reader; return 0; } gpg_error_t armor_data (char **r_string, const void *data, size_t datalen) { gpg_error_t err; - struct b64state b64state; + gpgrt_b64state_t b64state; estream_t fp; long length; char *buffer; size_t nread; *r_string = NULL; fp = es_fopenmem (0, "rw,samethread"); if (!fp) return gpg_error_from_syserror (); - if ((err=b64enc_start_es (&b64state, fp, "PGP PUBLIC KEY BLOCK")) - || (err=b64enc_write (&b64state, data, datalen)) - || (err = b64enc_finish (&b64state))) + b64state = gpgrt_b64enc_start (fp, "PGP PUBLIC KEY BLOCK"); + if (!b64state) + { + es_fclose (fp); + return gpg_error_from_syserror (); + } + + if ((err = gpgrt_b64enc_write (b64state, data, datalen)) + || (err = gpgrt_b64enc_finish (b64state))) { es_fclose (fp); return err; } /* FIXME: To avoid the extra buffer allocation estream should provide a function to snatch the internal allocated memory from such a memory stream. */ length = es_ftell (fp); if (length < 0) { err = gpg_error_from_syserror (); es_fclose (fp); return err; } buffer = xtrymalloc (length+1); if (!buffer) { err = gpg_error_from_syserror (); es_fclose (fp); return err; } es_rewind (fp); if (es_read (fp, buffer, length, &nread)) { err = gpg_error_from_syserror (); es_fclose (fp); return err; } buffer[nread] = 0; es_fclose (fp); *r_string = buffer; return 0; } /* Copy all data from IN to OUT. OUT may be NULL to use this function * as a dummy reader. */ gpg_error_t copy_stream (estream_t in, estream_t out) { char buffer[512]; size_t nread; while (!es_read (in, buffer, sizeof buffer, &nread)) { if (!nread) return 0; /* EOF */ if (out && es_write (out, buffer, nread, NULL)) break; } return gpg_error_from_syserror (); } diff --git a/g10/export.c b/g10/export.c index b05492b32..2c6eb7bff 100644 --- a/g10/export.c +++ b/g10/export.c @@ -1,3175 +1,3175 @@ /* export.c - Export keys in the OpenPGP defined format. * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, * 2005, 2010 Free Software Foundation, Inc. * Copyright (C) 1998-2016 Werner Koch * Copyright (C) 2022 g10 Code GmbH * * 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 "gpg.h" #include "options.h" #include "packet.h" #include "../common/status.h" #include "keydb.h" #include "../common/util.h" #include "main.h" #include "../common/i18n.h" #include "../common/membuf.h" #include "../common/host2net.h" #include "../common/zb32.h" #include "../common/recsel.h" #include "../common/mbox-util.h" #include "../common/init.h" #include "trustdb.h" #include "call-agent.h" #include "key-clean.h" #include "pkglue.h" /* An object to keep track of subkeys. */ struct subkey_list_s { struct subkey_list_s *next; u32 kid[2]; }; typedef struct subkey_list_s *subkey_list_t; /* An object to track statistics for export operations. */ struct export_stats_s { ulong count; /* Number of processed keys. */ ulong secret_count; /* Number of secret keys seen. */ ulong exported; /* Number of actual exported keys. */ }; /* Global variables to store the selectors created from * --export-filter keep-uid=EXPR. * --export-filter drop-subkey=EXPR. * --export-filter select=EXPR. * * FIXME: We should put this into the CTRL object but that requires a * lot more changes right now. */ static recsel_expr_t export_keep_uid; static recsel_expr_t export_drop_subkey; static recsel_expr_t export_select_filter; /* An object used for a linked list to implement the * push_export_filter/pop_export_filters functions. */ struct export_filter_attic_s { struct export_filter_attic_s *next; recsel_expr_t export_keep_uid; recsel_expr_t export_drop_subkey; recsel_expr_t export_select_filter; }; static struct export_filter_attic_s *export_filter_attic; /* Local prototypes. */ static int do_export (ctrl_t ctrl, strlist_t users, int secret, unsigned int options, export_stats_t stats); static int do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret, kbnode_t *keyblock_out, unsigned int options, export_stats_t stats, int *any); static gpg_error_t print_dane_records /**/ (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk, const void *data, size_t datalen); static void cleanup_export_globals (void) { recsel_release (export_keep_uid); export_keep_uid = NULL; recsel_release (export_drop_subkey); export_drop_subkey = NULL; recsel_release (export_select_filter); export_select_filter = NULL; } /* Option parser for export options. See parse_options for details. */ int parse_export_options(char *str,unsigned int *options,int noisy) { struct parse_options export_opts[]= { {"export-local-sigs",EXPORT_LOCAL_SIGS,NULL, N_("export signatures that are marked as local-only")}, {"export-attributes",EXPORT_ATTRIBUTES,NULL, N_("export attribute user IDs (generally photo IDs)")}, {"export-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL, N_("export revocation keys marked as \"sensitive\"")}, {"export-clean",EXPORT_CLEAN,NULL, N_("remove unusable parts from key during export")}, {"export-minimal",EXPORT_MINIMAL|EXPORT_CLEAN,NULL, N_("remove as much as possible from key during export")}, {"export-dane", EXPORT_DANE_FORMAT, NULL, NULL }, {"export-revocs", EXPORT_REVOCS, NULL, N_("export only revocation certificates") }, {"backup", EXPORT_BACKUP, NULL, N_("use the GnuPG key backup format")}, {"export-backup", EXPORT_BACKUP, NULL, NULL }, {"mode1003", EXPORT_MODE1003, NULL, N_("export secret keys using the GnuPG format") }, /* Aliases for backward compatibility */ {"include-local-sigs",EXPORT_LOCAL_SIGS,NULL,NULL}, {"include-attributes",EXPORT_ATTRIBUTES,NULL,NULL}, {"include-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL,NULL}, /* dummy */ {"export-unusable-sigs",0,NULL,NULL}, {"export-clean-sigs",0,NULL,NULL}, {"export-clean-uids",0,NULL,NULL}, {NULL,0,NULL,NULL} /* add tags for include revoked and disabled? */ }; int rc; rc = parse_options (str, options, export_opts, noisy); if (!rc) return 0; /* Alter other options we want or don't want for restore. */ if ((*options & EXPORT_BACKUP)) { *options |= (EXPORT_LOCAL_SIGS | EXPORT_ATTRIBUTES | EXPORT_SENSITIVE_REVKEYS); *options &= ~(EXPORT_CLEAN | EXPORT_MINIMAL | EXPORT_DANE_FORMAT); } return rc; } /* Parse and set an export filter from string. STRING has the format * "NAME=EXPR" with NAME being the name of the filter. Spaces before * and after NAME are not allowed. If this function is called several * times all expressions for the same NAME are concatenated. * Supported filter names are: * * - keep-uid :: If the expression evaluates to true for a certain * user ID packet, that packet and all it dependencies * will be exported. The expression may use these * variables: * * - uid :: The entire user ID. * - mbox :: The mail box part of the user ID. * - primary :: Evaluate to true for the primary user ID. * * - drop-subkey :: If the expression evaluates to true for a subkey * packet that subkey and all it dependencies will be * remove from the keyblock. The expression may use these * variables: * * - secret :: 1 for a secret subkey, else 0. * - key_algo :: Public key algorithm id * * - select :: The key is only exported if the filter returns true. */ gpg_error_t parse_and_set_export_filter (const char *string) { gpg_error_t err; /* Auto register the cleanup function. */ register_mem_cleanup_func (cleanup_export_globals); if (!strncmp (string, "keep-uid=", 9)) err = recsel_parse_expr (&export_keep_uid, string+9); else if (!strncmp (string, "drop-subkey=", 12)) err = recsel_parse_expr (&export_drop_subkey, string+12); else if (!strncmp (string, "select=", 7)) err = recsel_parse_expr (&export_select_filter, string+7); else err = gpg_error (GPG_ERR_INV_NAME); return err; } /* Push the current export filters onto a stack so that new export * filters can be defined which will be active until the next * pop_export_filters or another push_export_filters. */ void push_export_filters (void) { struct export_filter_attic_s *item; item = xcalloc (1, sizeof *item); item->export_keep_uid = export_keep_uid; export_keep_uid = NULL; item->export_drop_subkey = export_drop_subkey; export_drop_subkey = NULL; item->export_select_filter = export_select_filter; export_select_filter = NULL; item->next = export_filter_attic; export_filter_attic = item; } /* Revert the last push_export_filters. */ void pop_export_filters (void) { struct export_filter_attic_s *item; item = export_filter_attic; if (!item) BUG (); /* No corresponding push. */ export_filter_attic = item->next; cleanup_export_globals (); export_keep_uid = item->export_keep_uid; export_drop_subkey = item->export_drop_subkey; export_select_filter = item->export_select_filter; } /* Create a new export stats object initialized to zero. On error returns NULL and sets ERRNO. */ export_stats_t export_new_stats (void) { export_stats_t stats; return xtrycalloc (1, sizeof *stats); } /* Release an export stats object. */ void export_release_stats (export_stats_t stats) { xfree (stats); } /* Print export statistics using the status interface. */ void export_print_stats (export_stats_t stats) { if (!stats) return; if (is_status_enabled ()) { char buf[15*20]; snprintf (buf, sizeof buf, "%lu %lu %lu", stats->count, stats->secret_count, stats->exported ); write_status_text (STATUS_EXPORT_RES, buf); } } /* * Export public keys (to stdout or to --output FILE). * * Depending on opt.armor the output is armored. OPTIONS are defined * in main.h. If USERS is NULL, all keys will be exported. STATS is * either an export stats object for update or NULL. * * This function is the core of "gpg --export". */ int export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options, export_stats_t stats) { return do_export (ctrl, users, 0, options, stats); } /* * Export secret keys (to stdout or to --output FILE). * * Depending on opt.armor the output is armored. OPTIONS are defined * in main.h. If USERS is NULL, all secret keys will be exported. * STATS is either an export stats object for update or NULL. * * This function is the core of "gpg --export-secret-keys". */ int export_seckeys (ctrl_t ctrl, strlist_t users, unsigned int options, export_stats_t stats) { return do_export (ctrl, users, 1, options, stats); } /* * Export secret sub keys (to stdout or to --output FILE). * * This is the same as export_seckeys but replaces the primary key by * a stub key. Depending on opt.armor the output is armored. OPTIONS * are defined in main.h. If USERS is NULL, all secret subkeys will * be exported. STATS is either an export stats object for update or * NULL. * * This function is the core of "gpg --export-secret-subkeys". */ int export_secsubkeys (ctrl_t ctrl, strlist_t users, unsigned int options, export_stats_t stats) { return do_export (ctrl, users, 2, options, stats); } /* * Export a single key into a memory buffer. STATS is either an * export stats object for update or NULL. If PREFIX is not NULL * PREFIXLEN bytes from PREFIX are prepended to the R_DATA. */ gpg_error_t export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options, const void *prefix, size_t prefixlen, export_stats_t stats, kbnode_t *r_keyblock, void **r_data, size_t *r_datalen) { gpg_error_t err; iobuf_t iobuf; int any; strlist_t helplist; *r_keyblock = NULL; *r_data = NULL; *r_datalen = 0; helplist = NULL; if (!add_to_strlist_try (&helplist, keyspec)) return gpg_error_from_syserror (); iobuf = iobuf_temp (); if (prefix && prefixlen) iobuf_write (iobuf, prefix, prefixlen); err = do_export_stream (ctrl, iobuf, helplist, 0, r_keyblock, options, stats, &any); if (!err && !any) err = gpg_error (GPG_ERR_NOT_FOUND); if (!err) { const void *src; size_t datalen; iobuf_flush_temp (iobuf); src = iobuf_get_temp_buffer (iobuf); datalen = iobuf_get_temp_length (iobuf); if (!datalen) err = gpg_error (GPG_ERR_NO_PUBKEY); else if (!(*r_data = xtrymalloc (datalen))) err = gpg_error_from_syserror (); else { memcpy (*r_data, src, datalen); *r_datalen = datalen; } } iobuf_close (iobuf); free_strlist (helplist); if (err && *r_keyblock) { release_kbnode (*r_keyblock); *r_keyblock = NULL; } return err; } /* Export the keys identified by the list of strings in USERS. If Secret is false public keys will be exported. With secret true secret keys will be exported; in this case 1 means the entire secret keyblock and 2 only the subkeys. OPTIONS are the export options to apply. */ static int do_export (ctrl_t ctrl, strlist_t users, int secret, unsigned int options, export_stats_t stats) { IOBUF out = NULL; int any, rc; armor_filter_context_t *afx = NULL; compress_filter_context_t zfx; memset( &zfx, 0, sizeof zfx); rc = open_outfile (GNUPG_INVALID_FD, NULL, 0, !!secret, &out); if (rc) return rc; if ( opt.armor && !(options & EXPORT_DANE_FORMAT) ) { afx = new_armor_context (); afx->what = secret? 5 : 1; push_armor_filter (afx, out); } rc = do_export_stream (ctrl, out, users, secret, NULL, options, stats, &any); if ( rc || !any ) iobuf_cancel (out); else iobuf_close (out); release_armor_context (afx); return rc; } /* Release an entire subkey list. */ static void release_subkey_list (subkey_list_t list) { while (list) { subkey_list_t tmp = list->next;; xfree (list); list = tmp; } } /* Returns true if NODE is a subkey and contained in LIST. */ static int subkey_in_list_p (subkey_list_t list, KBNODE node) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY ) { u32 kid[2]; keyid_from_pk (node->pkt->pkt.public_key, kid); for (; list; list = list->next) if (list->kid[0] == kid[0] && list->kid[1] == kid[1]) return 1; } return 0; } /* Allocate a new subkey list item from NODE. */ static subkey_list_t new_subkey_list_item (KBNODE node) { subkey_list_t list = xcalloc (1, sizeof *list); if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) keyid_from_pk (node->pkt->pkt.public_key, list->kid); return list; } /* Helper function to check whether the subkey at NODE actually matches the description at DESC. The function returns true if the key under question has been specified by an exact specification (keyID or fingerprint) and does match the one at NODE. It is assumed that the packet at NODE is either a public or secret subkey. */ int exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, kbnode_t node) { u32 kid[2]; byte fpr[MAX_FINGERPRINT_LEN]; size_t fprlen; int result = 0; switch(desc->mode) { case KEYDB_SEARCH_MODE_SHORT_KID: case KEYDB_SEARCH_MODE_LONG_KID: keyid_from_pk (node->pkt->pkt.public_key, kid); break; case KEYDB_SEARCH_MODE_FPR: fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen); break; default: break; } switch(desc->mode) { case KEYDB_SEARCH_MODE_SHORT_KID: if (desc->u.kid[1] == kid[1]) result = 1; break; case KEYDB_SEARCH_MODE_LONG_KID: if (desc->u.kid[0] == kid[0] && desc->u.kid[1] == kid[1]) result = 1; break; case KEYDB_SEARCH_MODE_FPR: if (fprlen == desc->fprlen && !memcmp (desc->u.fpr, fpr, desc->fprlen)) result = 1; break; default: break; } return result; } /* Return an error if the key represented by the S-expression S_KEY * and the OpenPGP key represented by PK do not use the same curve. */ static gpg_error_t match_curve_skey_pk (gcry_sexp_t s_key, PKT_public_key *pk) { gcry_sexp_t curve = NULL; gcry_sexp_t flags = NULL; char *curve_str = NULL; char *flag; const char *oidstr = NULL; gcry_mpi_t curve_as_mpi = NULL; gpg_error_t err; int is_eddsa = 0; int idx = 0; if (!(pk->pubkey_algo==PUBKEY_ALGO_ECDH || pk->pubkey_algo==PUBKEY_ALGO_ECDSA || pk->pubkey_algo==PUBKEY_ALGO_EDDSA)) return gpg_error (GPG_ERR_PUBKEY_ALGO); curve = gcry_sexp_find_token (s_key, "curve", 0); if (!curve) { log_error ("no reported curve\n"); return gpg_error (GPG_ERR_UNKNOWN_CURVE); } curve_str = gcry_sexp_nth_string (curve, 1); gcry_sexp_release (curve); curve = NULL; if (!curve_str) { log_error ("no curve name\n"); return gpg_error (GPG_ERR_UNKNOWN_CURVE); } if (!strcmp (curve_str, "Ed448")) is_eddsa = 1; oidstr = openpgp_curve_to_oid (curve_str, NULL, NULL); if (!oidstr) { log_error ("no OID known for curve '%s'\n", curve_str); xfree (curve_str); return gpg_error (GPG_ERR_UNKNOWN_CURVE); } xfree (curve_str); err = openpgp_oid_from_str (oidstr, &curve_as_mpi); if (err) return err; if (gcry_mpi_cmp (pk->pkey[0], curve_as_mpi)) { log_error ("curves do not match\n"); gcry_mpi_release (curve_as_mpi); return gpg_error (GPG_ERR_INV_CURVE); } gcry_mpi_release (curve_as_mpi); flags = gcry_sexp_find_token (s_key, "flags", 0); if (flags) { for (idx = 1; idx < gcry_sexp_length (flags); idx++) { flag = gcry_sexp_nth_string (flags, idx); if (flag && (strcmp ("eddsa", flag) == 0)) is_eddsa = 1; gcry_free (flag); } } if (is_eddsa != (pk->pubkey_algo == PUBKEY_ALGO_EDDSA)) { log_error ("disagreement about EdDSA\n"); err = gpg_error (GPG_ERR_INV_CURVE); } return err; } /* Return a canonicalized public key algorithms. This is used to compare different flavors of algorithms (e.g. ELG and ELG_E are considered the same). */ static enum gcry_pk_algos canon_pk_algo (enum gcry_pk_algos algo) { switch (algo) { case GCRY_PK_RSA: case GCRY_PK_RSA_E: case GCRY_PK_RSA_S: return GCRY_PK_RSA; case GCRY_PK_ELG: case GCRY_PK_ELG_E: return GCRY_PK_ELG; case GCRY_PK_ECC: case GCRY_PK_ECDSA: case GCRY_PK_ECDH: return GCRY_PK_ECC; default: return algo; } } /* Take an s-expression wit the public and private key and change the * parameter array in PK to include the secret parameters. */ static gpg_error_t secret_key_to_mode1003 (gcry_sexp_t s_key, PKT_public_key *pk) { gpg_error_t err; gcry_sexp_t list = NULL; gcry_sexp_t l2; enum gcry_pk_algos pk_algo; struct seckey_info *ski; int idx; char *string; size_t npkey, nskey; gcry_mpi_t pub_params[10] = { NULL }; /* We look for a private-key, then the first element in it tells us the type */ list = gcry_sexp_find_token (s_key, "protected-private-key", 0); if (!list) list = gcry_sexp_find_token (s_key, "private-key", 0); if (!list) { err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } log_assert (!pk->seckey_info); /* Parse the gcrypt PK algo and check that it is okay. */ l2 = gcry_sexp_cadr (list); if (!l2) { err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } gcry_sexp_release (list); list = l2; string = gcry_sexp_nth_string (list, 0); if (!string) { err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } pk_algo = gcry_pk_map_name (string); xfree (string); string = NULL; if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey) || gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey) || !npkey || npkey >= nskey) { err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } /* Check that the pubkey algo and the received parameters matches * those from the public key. */ switch (canon_pk_algo (pk_algo)) { case GCRY_PK_RSA: if (!is_RSA (pk->pubkey_algo) || npkey != 2) err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */ else err = gcry_sexp_extract_param (list, NULL, "ne", &pub_params[0], &pub_params[1], NULL); break; case GCRY_PK_DSA: if (!is_DSA (pk->pubkey_algo) || npkey != 4) err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */ else err = gcry_sexp_extract_param (list, NULL, "pqgy", &pub_params[0], &pub_params[1], &pub_params[2], &pub_params[3], NULL); break; case GCRY_PK_ELG: if (!is_ELGAMAL (pk->pubkey_algo) || npkey != 3) err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */ else err = gcry_sexp_extract_param (list, NULL, "pgy", &pub_params[0], &pub_params[1], &pub_params[2], NULL); break; case GCRY_PK_ECC: err = 0; if (!(pk->pubkey_algo == PUBKEY_ALGO_ECDSA || pk->pubkey_algo == PUBKEY_ALGO_ECDH || pk->pubkey_algo == PUBKEY_ALGO_EDDSA)) { err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */ goto leave; } npkey = 2; if (pk->pubkey_algo == PUBKEY_ALGO_ECDH) npkey++; /* Dedicated check of the curve. */ pub_params[0] = NULL; err = match_curve_skey_pk (list, pk); if (err) goto leave; /* ... and of the Q parameter. */ err = sexp_extract_param_sos (list, "q", &pub_params[1]); if (!err && (gcry_mpi_cmp (pk->pkey[1], pub_params[1]))) err = gpg_error (GPG_ERR_BAD_PUBKEY); break; default: err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Unknown. */ break; } if (err) goto leave; nskey = npkey + 1; /* We only have one skey param. */ if (nskey > PUBKEY_MAX_NSKEY) { err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } /* Check that the public key parameters match. For ECC we already * did this in the switch above. */ if (canon_pk_algo (pk_algo) != GCRY_PK_ECC) { for (idx=0; idx < npkey; idx++) if (gcry_mpi_cmp (pk->pkey[idx], pub_params[idx])) { err = gpg_error (GPG_ERR_BAD_PUBKEY); goto leave; } } /* Store the maybe protected secret key as an s-expression. */ pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); if (!ski) { err = gpg_error_from_syserror (); goto leave; } pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); if (!ski) { err = gpg_error_from_syserror (); goto leave; } ski->is_protected = 1; ski->s2k.mode = 1003; { unsigned char *buf; size_t buflen; err = make_canon_sexp (s_key, &buf, &buflen); if (err) goto leave; pk->pkey[npkey] = gcry_mpi_set_opaque (NULL, buf, buflen*8); for (idx=npkey+1; idx < PUBKEY_MAX_NSKEY; idx++) pk->pkey[idx] = NULL; } leave: gcry_sexp_release (list); for (idx=0; idx < DIM(pub_params); idx++) gcry_mpi_release (pub_params[idx]); return err; } /* Take a cleartext dump of a secret key in PK and change the * parameter array in PK to include the secret parameters. */ static gpg_error_t cleartext_secret_key_to_openpgp (gcry_sexp_t s_key, PKT_public_key *pk) { gpg_error_t err; gcry_sexp_t top_list; gcry_sexp_t key = NULL; char *key_type = NULL; enum gcry_pk_algos pk_algo; struct seckey_info *ski; int idx, sec_start; gcry_mpi_t pub_params[10] = { NULL }; /* we look for a private-key, then the first element in it tells us the type */ top_list = gcry_sexp_find_token (s_key, "private-key", 0); if (!top_list) goto bad_seckey; /* ignore all S-expression after the first sublist -- we assume that they are comments or otherwise irrelevant to OpenPGP */ if (gcry_sexp_length(top_list) < 2) goto bad_seckey; key = gcry_sexp_nth (top_list, 1); if (!key) goto bad_seckey; key_type = gcry_sexp_nth_string(key, 0); pk_algo = gcry_pk_map_name (key_type); log_assert (!pk->seckey_info); pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); if (!ski) { err = gpg_error_from_syserror (); goto leave; } switch (canon_pk_algo (pk_algo)) { case GCRY_PK_RSA: if (!is_RSA (pk->pubkey_algo)) goto bad_pubkey_algo; err = gcry_sexp_extract_param (key, NULL, "ne", &pub_params[0], &pub_params[1], NULL); for (idx=0; idx < 2 && !err; idx++) if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx])) err = gpg_error (GPG_ERR_BAD_PUBKEY); if (!err) { for (idx = 2; idx < 6 && !err; idx++) { gcry_mpi_release (pk->pkey[idx]); pk->pkey[idx] = NULL; } err = gcry_sexp_extract_param (key, NULL, "dpqu", &pk->pkey[2], &pk->pkey[3], &pk->pkey[4], &pk->pkey[5], NULL); } if (!err) { for (idx = 2; idx < 6; idx++) ski->csum += checksum_mpi (pk->pkey[idx]); } break; case GCRY_PK_DSA: if (!is_DSA (pk->pubkey_algo)) goto bad_pubkey_algo; err = gcry_sexp_extract_param (key, NULL, "pqgy", &pub_params[0], &pub_params[1], &pub_params[2], &pub_params[3], NULL); for (idx=0; idx < 4 && !err; idx++) if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx])) err = gpg_error (GPG_ERR_BAD_PUBKEY); if (!err) { gcry_mpi_release (pk->pkey[4]); pk->pkey[4] = NULL; err = gcry_sexp_extract_param (key, NULL, "x", &pk->pkey[4], NULL); } if (!err) ski->csum += checksum_mpi (pk->pkey[4]); break; case GCRY_PK_ELG: if (!is_ELGAMAL (pk->pubkey_algo)) goto bad_pubkey_algo; err = gcry_sexp_extract_param (key, NULL, "pgy", &pub_params[0], &pub_params[1], &pub_params[2], NULL); for (idx=0; idx < 3 && !err; idx++) if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx])) err = gpg_error (GPG_ERR_BAD_PUBKEY); if (!err) { gcry_mpi_release (pk->pkey[3]); pk->pkey[3] = NULL; err = gcry_sexp_extract_param (key, NULL, "x", &pk->pkey[3], NULL); } if (!err) ski->csum += checksum_mpi (pk->pkey[3]); break; case GCRY_PK_ECC: err = match_curve_skey_pk (key, pk); if (err) goto leave; else err = sexp_extract_param_sos (key, "q", &pub_params[0]); if (!err && (gcry_mpi_cmp(pk->pkey[1], pub_params[0]))) err = gpg_error (GPG_ERR_BAD_PUBKEY); sec_start = 2; if (pk->pubkey_algo == PUBKEY_ALGO_ECDH) sec_start += 1; if (!err) { gcry_mpi_release (pk->pkey[sec_start]); pk->pkey[sec_start] = NULL; err = sexp_extract_param_sos (key, "d", &pk->pkey[sec_start]); } if (!err) ski->csum += checksum_mpi (pk->pkey[sec_start]); break; default: pk->seckey_info = NULL; xfree (ski); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); break; } leave: gcry_sexp_release (top_list); gcry_sexp_release (key); gcry_free (key_type); for (idx=0; idx < DIM(pub_params); idx++) gcry_mpi_release (pub_params[idx]); return err; bad_pubkey_algo: err = gpg_error (GPG_ERR_PUBKEY_ALGO); goto leave; bad_seckey: err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; } /* Use the key transfer format given in S_PGP to create the secinfo structure in PK and change the parameter array in PK to include the secret parameters. */ static gpg_error_t transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk) { gpg_error_t err; gcry_sexp_t top_list; gcry_sexp_t list = NULL; char *curve = NULL; const char *value; size_t valuelen; char *string; int idx; int is_v4, is_protected; enum gcry_pk_algos pk_algo; int protect_algo = 0; char iv[16]; int ivlen = 0; int s2k_mode = 0; int s2k_algo = 0; byte s2k_salt[8]; u32 s2k_count = 0; int is_ecdh = 0; size_t npkey, nskey; gcry_mpi_t skey[10]; /* We support up to 9 parameters. */ int skeyidx = 0; struct seckey_info *ski; /* gcry_log_debugsxp ("transferkey", s_pgp); */ top_list = gcry_sexp_find_token (s_pgp, "openpgp-private-key", 0); if (!top_list) goto bad_seckey; list = gcry_sexp_find_token (top_list, "version", 0); if (!list) goto bad_seckey; value = gcry_sexp_nth_data (list, 1, &valuelen); if (!value || valuelen != 1 || !(value[0] == '3' || value[0] == '4')) goto bad_seckey; is_v4 = (value[0] == '4'); gcry_sexp_release (list); list = gcry_sexp_find_token (top_list, "protection", 0); if (!list) goto bad_seckey; value = gcry_sexp_nth_data (list, 1, &valuelen); if (!value) goto bad_seckey; if (valuelen == 4 && !memcmp (value, "sha1", 4)) is_protected = 2; else if (valuelen == 3 && !memcmp (value, "sum", 3)) is_protected = 1; else if (valuelen == 4 && !memcmp (value, "none", 4)) is_protected = 0; else goto bad_seckey; if (is_protected) { string = gcry_sexp_nth_string (list, 2); if (!string) goto bad_seckey; protect_algo = gcry_cipher_map_name (string); xfree (string); value = gcry_sexp_nth_data (list, 3, &valuelen); if (!value || !valuelen || valuelen > sizeof iv) goto bad_seckey; memcpy (iv, value, valuelen); ivlen = valuelen; string = gcry_sexp_nth_string (list, 4); if (!string) goto bad_seckey; s2k_mode = strtol (string, NULL, 10); xfree (string); string = gcry_sexp_nth_string (list, 5); if (!string) goto bad_seckey; s2k_algo = gcry_md_map_name (string); xfree (string); value = gcry_sexp_nth_data (list, 6, &valuelen); if (!value || !valuelen || valuelen > sizeof s2k_salt) goto bad_seckey; memcpy (s2k_salt, value, valuelen); string = gcry_sexp_nth_string (list, 7); if (!string) goto bad_seckey; s2k_count = strtoul (string, NULL, 10); xfree (string); } /* Parse the gcrypt PK algo and check that it is okay. */ gcry_sexp_release (list); list = gcry_sexp_find_token (top_list, "algo", 0); if (!list) goto bad_seckey; string = gcry_sexp_nth_string (list, 1); if (!string) goto bad_seckey; pk_algo = gcry_pk_map_name (string); xfree (string); string = NULL; if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey) || gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey) || !npkey || npkey >= nskey) goto bad_seckey; /* Check that the pubkey algo matches the one from the public key. */ switch (canon_pk_algo (pk_algo)) { case GCRY_PK_RSA: if (!is_RSA (pk->pubkey_algo)) pk_algo = 0; /* Does not match. */ break; case GCRY_PK_DSA: if (!is_DSA (pk->pubkey_algo)) pk_algo = 0; /* Does not match. */ break; case GCRY_PK_ELG: if (!is_ELGAMAL (pk->pubkey_algo)) pk_algo = 0; /* Does not match. */ break; case GCRY_PK_ECC: if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA) ; else if (pk->pubkey_algo == PUBKEY_ALGO_ECDH) is_ecdh = 1; else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA) ; else pk_algo = 0; /* Does not match. */ /* For ECC we do not have the domain parameters thus fix our info. */ npkey = 1; nskey = 2; break; default: pk_algo = 0; /* Oops. */ break; } if (!pk_algo) { err = gpg_error (GPG_ERR_PUBKEY_ALGO); goto leave; } /* This check has to go after the ecc adjustments. */ if (nskey > PUBKEY_MAX_NSKEY) goto bad_seckey; /* Parse the key parameters. */ gcry_sexp_release (list); list = gcry_sexp_find_token (top_list, "skey", 0); if (!list) goto bad_seckey; for (idx=0;;) { int is_enc; value = gcry_sexp_nth_data (list, ++idx, &valuelen); if (!value && skeyidx >= npkey) break; /* Ready. */ /* Check for too many parameters. Note that depending on the protection mode and version number we may see less than NSKEY (but at least NPKEY+1) parameters. */ if (idx >= 2*nskey) goto bad_seckey; if (skeyidx >= DIM (skey)-1) goto bad_seckey; if (!value || valuelen != 1 || !(value[0] == '_' || value[0] == 'e')) goto bad_seckey; is_enc = (value[0] == 'e'); value = gcry_sexp_nth_data (list, ++idx, &valuelen); if (!value || !valuelen) goto bad_seckey; if (is_enc || pk->pubkey_algo == PUBKEY_ALGO_ECDSA || pk->pubkey_algo == PUBKEY_ALGO_EDDSA || pk->pubkey_algo == PUBKEY_ALGO_ECDH) { unsigned int nbits = valuelen*8; const unsigned char *p = value; if (*p && nbits >= 8 && !(*p & 0x80)) if (--nbits >= 7 && !(*p & 0x40)) if (--nbits >= 6 && !(*p & 0x20)) if (--nbits >= 5 && !(*p & 0x10)) if (--nbits >= 4 && !(*p & 0x08)) if (--nbits >= 3 && !(*p & 0x04)) if (--nbits >= 2 && !(*p & 0x02)) if (--nbits >= 1 && !(*p & 0x01)) --nbits; skey[skeyidx] = gcry_mpi_set_opaque_copy (NULL, value, nbits); if (!skey[skeyidx]) goto outofmem; if (is_enc) gcry_mpi_set_flag (skey[skeyidx], GCRYMPI_FLAG_USER1); else gcry_mpi_set_flag (skey[skeyidx], GCRYMPI_FLAG_USER2); } else { if (gcry_mpi_scan (skey + skeyidx, GCRYMPI_FMT_STD, value, valuelen, NULL)) goto bad_seckey; } skeyidx++; } skey[skeyidx++] = NULL; gcry_sexp_release (list); list = NULL; /* We have no need for the CSUM value thus we don't parse it. */ /* list = gcry_sexp_find_token (top_list, "csum", 0); */ /* if (list) */ /* { */ /* string = gcry_sexp_nth_string (list, 1); */ /* if (!string) */ /* goto bad_seckey; */ /* desired_csum = strtoul (string, NULL, 10); */ /* xfree (string); */ /* } */ /* else */ /* desired_csum = 0; */ /* gcry_sexp_release (list); list = NULL; */ /* Get the curve name if any, */ list = gcry_sexp_find_token (top_list, "curve", 0); if (list) { curve = gcry_sexp_nth_string (list, 1); gcry_sexp_release (list); list = NULL; } gcry_sexp_release (top_list); top_list = NULL; /* log_debug ("XXX is_v4=%d\n", is_v4); */ /* log_debug ("XXX pubkey_algo=%d\n", pubkey_algo); */ /* log_debug ("XXX is_protected=%d\n", is_protected); */ /* log_debug ("XXX protect_algo=%d\n", protect_algo); */ /* log_printhex ("XXX iv", iv, ivlen); */ /* log_debug ("XXX ivlen=%d\n", ivlen); */ /* log_debug ("XXX s2k_mode=%d\n", s2k_mode); */ /* log_debug ("XXX s2k_algo=%d\n", s2k_algo); */ /* log_printhex ("XXX s2k_salt", s2k_salt, sizeof s2k_salt); */ /* log_debug ("XXX s2k_count=%lu\n", (unsigned long)s2k_count); */ /* for (idx=0; skey[idx]; idx++) */ /* { */ /* int is_enc = gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE); */ /* log_info ("XXX skey[%d]%s:", idx, is_enc? " (enc)":""); */ /* if (is_enc) */ /* { */ /* void *p; */ /* unsigned int nbits; */ /* p = gcry_mpi_get_opaque (skey[idx], &nbits); */ /* log_printhex (NULL, p, (nbits+7)/8); */ /* } */ /* else */ /* gcry_mpi_dump (skey[idx]); */ /* log_printf ("\n"); */ /* } */ if (!is_v4 || is_protected != 2 ) { /* We only support the v4 format and a SHA-1 checksum. */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); goto leave; } /* We need to change the received parameters for ECC algorithms. The transfer format has the curve name and the parameters separate. We put them all into the SKEY array. */ if (canon_pk_algo (pk_algo) == GCRY_PK_ECC) { const char *oidstr; /* Assert that all required parameters are available. We also check that the array does not contain more parameters than needed (this was used by some beta versions of 2.1. */ if (!curve || !skey[0] || !skey[1] || skey[2]) { err = gpg_error (GPG_ERR_INTERNAL); goto leave; } oidstr = openpgp_curve_to_oid (curve, NULL, NULL); if (!oidstr) { log_error ("no OID known for curve '%s'\n", curve); err = gpg_error (GPG_ERR_UNKNOWN_CURVE); goto leave; } /* Put the curve's OID into the MPI array. This requires that we shift Q and D. For ECDH also insert the KDF parms. */ if (is_ecdh) { skey[4] = NULL; skey[3] = skey[1]; skey[2] = gcry_mpi_copy (pk->pkey[2]); } else { skey[3] = NULL; skey[2] = skey[1]; } skey[1] = skey[0]; skey[0] = NULL; err = openpgp_oid_from_str (oidstr, skey + 0); if (err) goto leave; /* Fixup the NPKEY and NSKEY to match OpenPGP reality. */ npkey = 2 + is_ecdh; nskey = 3 + is_ecdh; /* for (idx=0; skey[idx]; idx++) */ /* { */ /* log_info ("YYY skey[%d]:", idx); */ /* if (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE)) */ /* { */ /* void *p; */ /* unsigned int nbits; */ /* p = gcry_mpi_get_opaque (skey[idx], &nbits); */ /* log_printhex (NULL, p, (nbits+7)/8); */ /* } */ /* else */ /* gcry_mpi_dump (skey[idx]); */ /* log_printf ("\n"); */ /* } */ } /* Do some sanity checks. */ if (s2k_count > 255) { /* We expect an already encoded S2K count. */ err = gpg_error (GPG_ERR_INV_DATA); goto leave; } err = openpgp_cipher_test_algo (protect_algo); if (err) goto leave; err = openpgp_md_test_algo (s2k_algo); if (err) goto leave; /* Check that the public key parameters match. Note that since Libgcrypt 1.5 gcry_mpi_cmp handles opaque MPI correctly. */ for (idx=0; idx < npkey; idx++) if (gcry_mpi_cmp (pk->pkey[idx], skey[idx])) { err = gpg_error (GPG_ERR_BAD_PUBKEY); goto leave; } /* Check that the first secret key parameter in SKEY is encrypted and that there are no more secret key parameters. The latter is guaranteed by the v4 packet format. */ if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_USER1)) goto bad_seckey; if (npkey+1 < DIM (skey) && skey[npkey+1]) goto bad_seckey; /* Check that the secret key parameters in PK are all set to NULL. */ for (idx=npkey; idx < nskey; idx++) if (pk->pkey[idx]) goto bad_seckey; /* Now build the protection info. */ pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); if (!ski) { err = gpg_error_from_syserror (); goto leave; } ski->is_protected = 1; ski->sha1chk = 1; ski->algo = protect_algo; ski->s2k.mode = s2k_mode; ski->s2k.hash_algo = s2k_algo; log_assert (sizeof ski->s2k.salt == sizeof s2k_salt); memcpy (ski->s2k.salt, s2k_salt, sizeof s2k_salt); ski->s2k.count = s2k_count; log_assert (ivlen <= sizeof ski->iv); memcpy (ski->iv, iv, ivlen); ski->ivlen = ivlen; /* Store the protected secret key parameter. */ pk->pkey[npkey] = skey[npkey]; skey[npkey] = NULL; /* That's it. */ leave: gcry_free (curve); gcry_sexp_release (list); gcry_sexp_release (top_list); for (idx=0; idx < skeyidx; idx++) gcry_mpi_release (skey[idx]); return err; bad_seckey: err = gpg_error (GPG_ERR_BAD_SECKEY); goto leave; outofmem: err = gpg_error (GPG_ERR_ENOMEM); goto leave; } /* Print an "EXPORTED" status line. PK is the primary public key. */ static void print_status_exported (PKT_public_key *pk) { char *hexfpr; if (!is_status_enabled ()) return; hexfpr = hexfingerprint (pk, NULL, 0); write_status_text (STATUS_EXPORTED, hexfpr? hexfpr : "[?]"); xfree (hexfpr); } /* * Receive a secret key from agent specified by HEXGRIP. * * Since the key data from the agent is encrypted, decrypt it using * CIPHERHD context. Then, parse the decrypted key data into transfer * format, and put secret parameters into PK. * * If CLEARTEXT is 0, store the secret key material * passphrase-protected. Otherwise, store secret key material in the * clear. * * If MODE1003 is set, the key is requested in raw GnuPG format from * the agent. This usually does not require a passphrase unless the * gpg-agent has not yet used the key and needs to convert it to its * internal format first. * * CACHE_NONCE_ADDR is used to share nonce for multiple key retrievals. * * If PK is NULL, the raw key is returned (e.g. for ssh export) at * R_KEY. CLEARTEXT and CACHE_NONCE_ADDR ared ignored in this case. */ gpg_error_t receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd, int cleartext, int mode1003, char **cache_nonce_addr, const char *hexgrip, PKT_public_key *pk, gcry_sexp_t *r_key) { gpg_error_t err = 0; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; unsigned char *key = NULL; size_t keylen, realkeylen; gcry_sexp_t s_skey = NULL; char *prompt; if (r_key) *r_key = NULL; if (opt.verbose) log_info ("key %s: asking agent for the secret parts\n", hexgrip); if (pk) { prompt = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_EXPORT, 1); err = agent_export_key (ctrl, hexgrip, prompt, !cleartext, mode1003, cache_nonce_addr, &wrappedkey, &wrappedkeylen, pk->keyid, pk->main_keyid, pk->pubkey_algo); } else { prompt = gpg_format_keydesc (ctrl, NULL, FORMAT_KEYDESC_KEYGRIP, 1); err = agent_export_key (ctrl, hexgrip, prompt, 0, 0, NULL, &wrappedkey, &wrappedkeylen, NULL, NULL, 0); } xfree (prompt); if (err) goto unwraperror; if (wrappedkeylen < 24) { err = gpg_error (GPG_ERR_INV_LENGTH); goto unwraperror; } keylen = wrappedkeylen - 8; key = xtrymalloc_secure (keylen); if (!key) { err = gpg_error_from_syserror (); goto unwraperror; } err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen); if (err) goto unwraperror; realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err); if (!realkeylen) goto unwraperror; /* Invalid csexp. */ err = gcry_sexp_sscan (&s_skey, NULL, key, realkeylen); if (!err) { if (pk && mode1003) err = secret_key_to_mode1003 (s_skey, pk); else if (pk && cleartext) err = cleartext_secret_key_to_openpgp (s_skey, pk); else if (pk) err = transfer_format_to_openpgp (s_skey, pk); else if (r_key) { *r_key = s_skey; s_skey = NULL; } } unwraperror: gcry_sexp_release (s_skey); xfree (key); xfree (wrappedkey); if (err) { log_error ("key %s: error receiving key from agent:" " %s%s\n", hexgrip, gpg_strerror (err), gpg_err_code (err) == GPG_ERR_FULLY_CANCELED? "":_(" - skipped")); } return err; } /* Write KEYBLOCK either to stdout or to the file set with the * --output option. This is a simplified version of do_export_stream * which supports only a few export options. */ gpg_error_t write_keyblock_to_output (kbnode_t keyblock, int with_armor, unsigned int options) { gpg_error_t err; const char *fname; iobuf_t out; kbnode_t node; armor_filter_context_t *afx = NULL; iobuf_t out_help = NULL; PKT_public_key *pk = NULL; fname = opt.outfile? opt.outfile : "-"; if (is_secured_filename (fname) ) return gpg_error (GPG_ERR_EPERM); out = iobuf_create (fname, 0); if (!out) { err = gpg_error_from_syserror (); log_error(_("can't create '%s': %s\n"), fname, gpg_strerror (err)); return err; } if (opt.verbose) log_info (_("writing to '%s'\n"), iobuf_get_fname_nonnull (out)); if ((options & EXPORT_DANE_FORMAT)) { with_armor = 0; out_help = iobuf_temp (); } if (with_armor) { afx = new_armor_context (); afx->what = 1; push_armor_filter (afx, out); } for (node = keyblock; node; node = node->next) { if (is_deleted_kbnode (node)) continue; if (node->pkt->pkttype == PKT_RING_TRUST) continue; /* Skip - they should not be here anyway. */ if (!pk && (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_SECRET_KEY)) pk = node->pkt->pkt.public_key; if ((options & EXPORT_BACKUP)) err = build_packet_and_meta (out_help? out_help : out, node->pkt); else err = build_packet (out_help? out_help : out, node->pkt); if (err) { log_error ("build_packet(%d) failed: %s\n", node->pkt->pkttype, gpg_strerror (err) ); goto leave; } } err = 0; if (out_help && pk && (options & EXPORT_DANE_FORMAT)) { const void *data; size_t datalen; iobuf_flush_temp (out_help); data = iobuf_get_temp_buffer (out_help); datalen = iobuf_get_temp_length (out_help); err = print_dane_records (out, keyblock, pk, data, datalen); } leave: if (err) iobuf_cancel (out); else iobuf_close (out); iobuf_cancel (out_help); release_armor_context (afx); return err; } /* * Apply the keep-uid filter to the keyblock. The deleted nodes are * marked and thus the caller should call commit_kbnode afterwards. * KEYBLOCK must not have any blocks marked as deleted. */ static void apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector) { kbnode_t node; struct impex_filter_parm_s parm; parm.ctrl = ctrl; for (node = keyblock->next; node; node = node->next ) { if (node->pkt->pkttype == PKT_USER_ID) { parm.node = node; if (!recsel_select (selector, impex_filter_getval, &parm)) { /* log_debug ("keep-uid: deleting '%s'\n", */ /* node->pkt->pkt.user_id->name); */ /* The UID packet and all following packets up to the * next UID or a subkey. */ delete_kbnode (node); for (; node->next && node->next->pkt->pkttype != PKT_USER_ID && node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY && node->next->pkt->pkttype != PKT_SECRET_SUBKEY ; node = node->next) delete_kbnode (node->next); } /* else */ /* log_debug ("keep-uid: keeping '%s'\n", */ /* node->pkt->pkt.user_id->name); */ } } } /* * Apply the drop-subkey filter to the keyblock. The deleted nodes are * marked and thus the caller should call commit_kbnode afterwards. * KEYBLOCK must not have any blocks marked as deleted. */ static void apply_drop_subkey_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector) { kbnode_t node; struct impex_filter_parm_s parm; parm.ctrl = ctrl; for (node = keyblock->next; node; node = node->next ) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) { parm.node = node; if (recsel_select (selector, impex_filter_getval, &parm)) { /*log_debug ("drop-subkey: deleting a key\n");*/ /* The subkey packet and all following packets up to the * next subkey. */ delete_kbnode (node); for (; node->next && node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY && node->next->pkt->pkttype != PKT_SECRET_SUBKEY ; node = node->next) delete_kbnode (node->next); } } } } /* Print DANErecords for all user IDs in KEYBLOCK to OUT. The data * for the record is taken from (DATA,DATELEN). PK is the public key * packet with the primary key. */ static gpg_error_t print_dane_records (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk, const void *data, size_t datalen) { gpg_error_t err = 0; kbnode_t kbctx, node; PKT_user_id *uid; char *mbox = NULL; char hashbuf[32]; char *hash = NULL; char *domain; const char *s; unsigned int len; estream_t fp = NULL; char *hexdata = NULL; char *hexfpr; hexfpr = hexfingerprint (pk, NULL, 0); if (!hexfpr) { err = gpg_error_from_syserror (); goto leave; } hexdata = bin2hex (data, datalen, NULL); if (!hexdata) { err = gpg_error_from_syserror (); goto leave; } ascii_strlwr (hexdata); fp = es_fopenmem (0, "rw,samethread"); if (!fp) { err = gpg_error_from_syserror (); goto leave; } for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));) { if (node->pkt->pkttype != PKT_USER_ID) continue; uid = node->pkt->pkt.user_id; if (uid->flags.expired || uid->flags.revoked) continue; xfree (mbox); mbox = mailbox_from_userid (uid->name, 0); if (!mbox) continue; domain = strchr (mbox, '@'); *domain++ = 0; if (1) { es_fprintf (fp, "$ORIGIN _openpgpkey.%s.\n; %s\n; ", domain, hexfpr); print_utf8_buffer (fp, uid->name, uid->len); es_putc ('\n', fp); gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox)); xfree (hash); hash = bin2hex (hashbuf, 28, NULL); if (!hash) { err = gpg_error_from_syserror (); goto leave; } ascii_strlwr (hash); len = strlen (hexdata)/2; es_fprintf (fp, "%s TYPE61 \\# %u (\n", hash, len); for (s = hexdata; ;) { es_fprintf (fp, "\t%.64s\n", s); if (strlen (s) < 64) break; s += 64; } es_fputs ("\t)\n\n", fp); } } /* Make sure it is a string and write it. */ es_fputc (0, fp); { void *vp; if (es_fclose_snatch (fp, &vp, NULL)) { err = gpg_error_from_syserror (); goto leave; } fp = NULL; iobuf_writestr (out, vp); es_free (vp); } err = 0; leave: xfree (hash); xfree (mbox); es_fclose (fp); xfree (hexdata); xfree (hexfpr); return err; } /* Helper for do_export_stream which writes one keyblock to OUT. */ static gpg_error_t do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, iobuf_t out, int secret, unsigned int options, export_stats_t stats, int *any, KEYDB_SEARCH_DESC *desc, size_t ndesc, size_t descindex, gcry_cipher_hd_t cipherhd) { gpg_error_t err = gpg_error (GPG_ERR_NOT_FOUND); char *cache_nonce = NULL; subkey_list_t subkey_list = NULL; /* Track already processed subkeys. */ int skip_until_subkey = 0; int cleartext = 0; char *hexgrip = NULL; char *serialno = NULL; PKT_public_key *pk; u32 subkidbuf[2], *subkid; kbnode_t kbctx, node; /* NB: walk_kbnode skips packets marked as deleted. */ for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); ) { if (skip_until_subkey) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) skip_until_subkey = 0; else continue; } /* We used to use comment packets, but not any longer. In * case we still have comments on a key, strip them here * before we call build_packet(). */ if (node->pkt->pkttype == PKT_COMMENT) continue; /* Skip ring trust packets - they should not be here anyway. */ if (node->pkt->pkttype == PKT_RING_TRUST) continue; /* If exact is set, then we only export what was requested * (plus the primary key, if the user didn't specifically * request it). */ if (desc[descindex].exact && node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { if (!exact_subkey_match_p (desc+descindex, node)) { /* Before skipping this subkey, check whether any * other description wants an exact match on a * subkey and include that subkey into the output * too. Need to add this subkey to a list so that * it won't get processed a second time. * * So the first step here is to check that list and * skip in any case if the key is in that list. * * We need this whole mess because the import * function of GnuPG < 2.1 is not able to merge * secret keys and thus it is useless to output them * as two separate keys and have import merge them. */ if (subkey_in_list_p (subkey_list, node)) skip_until_subkey = 1; /* Already processed this one. */ else { size_t j; for (j=0; j < ndesc; j++) if (j != descindex && desc[j].exact && exact_subkey_match_p (desc+j, node)) break; if (!(j < ndesc)) skip_until_subkey = 1; /* No other one matching. */ } } if (skip_until_subkey) continue; /* Mark this one as processed. */ { subkey_list_t tmp = new_subkey_list_item (node); tmp->next = subkey_list; subkey_list = tmp; } } if (node->pkt->pkttype == PKT_SIGNATURE) { /* Do not export packets which are marked as not * exportable. */ if (!(options & EXPORT_LOCAL_SIGS) && !node->pkt->pkt.signature->flags.exportable) continue; /* not exportable */ /* Do not export packets with a "sensitive" revocation key * unless the user wants us to. Note that we do export * these when issuing the actual revocation (see revoke.c). */ if (!(options & EXPORT_SENSITIVE_REVKEYS) && node->pkt->pkt.signature->revkey) { int i; for (i = 0; i < node->pkt->pkt.signature->numrevkeys; i++) if ((node->pkt->pkt.signature->revkey[i].class & 0x40)) break; if (i < node->pkt->pkt.signature->numrevkeys) continue; } } /* Don't export attribs? */ if (!(options & EXPORT_ATTRIBUTES) && node->pkt->pkttype == PKT_USER_ID && node->pkt->pkt.user_id->attrib_data) { /* Skip until we get to something that is not an attrib or a * signature on an attrib. */ while (kbctx->next && kbctx->next->pkt->pkttype == PKT_SIGNATURE) kbctx = kbctx->next; continue; } if (secret && (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY)) { pk = node->pkt->pkt.public_key; if (node->pkt->pkttype == PKT_PUBLIC_KEY) subkid = NULL; else { keyid_from_pk (pk, subkidbuf); subkid = subkidbuf; } if (pk->seckey_info) { log_error ("key %s: oops: seckey_info already set" " - skipped\n", keystr_with_sub (keyid, subkid)); skip_until_subkey = 1; continue; } xfree (hexgrip); err = hexkeygrip_from_pk (pk, &hexgrip); if (err) { log_error ("key %s: error computing keygrip: %s" " - skipped\n", keystr_with_sub (keyid, subkid), gpg_strerror (err)); skip_until_subkey = 1; err = 0; continue; } xfree (serialno); serialno = NULL; if (secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY) { /* We are asked not to export the secret parts of the * primary key. Make up an error code to create the * stub. */ err = GPG_ERR_NOT_FOUND; } else err = agent_get_keyinfo (ctrl, hexgrip, &serialno, &cleartext); if ((!err && serialno) && secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY) { /* It does not make sense to export a key with its * primary key on card using a non-key stub. Thus we * skip those keys when used with --export-secret-subkeys. */ log_info (_("key %s: key material on-card - skipped\n"), keystr_with_sub (keyid, subkid)); skip_until_subkey = 1; } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND || (!err && serialno)) { /* Create a key stub. */ struct seckey_info *ski; const char *s; pk->seckey_info = ski = xtrycalloc (1, sizeof *ski); if (!ski) { err = gpg_error_from_syserror (); goto leave; } ski->is_protected = 1; if (err) ski->s2k.mode = 1001; /* GNU dummy (no secret key). */ else { ski->s2k.mode = 1002; /* GNU-divert-to-card. */ for (s=serialno; sizeof (ski->ivlen) && *s && s[1]; ski->ivlen++, s += 2) ski->iv[ski->ivlen] = xtoi_2 (s); } if ((options & EXPORT_BACKUP)) err = build_packet_and_meta (out, node->pkt); else err = build_packet (out, node->pkt); if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY) { stats->exported++; print_status_exported (node->pkt->pkt.public_key); } } else if (!err) { err = receive_seckey_from_agent (ctrl, cipherhd, cleartext, !!(options & EXPORT_MODE1003), &cache_nonce, hexgrip, pk, NULL); if (err) { /* If we receive a fully canceled error we stop * immediately. If we receive a cancel for a public * key we also stop immediately because a * public/secret key is always required first * (right, we could instead write a stub key but * that is also kind of surprising). If we receive * a subkey we skip to the next subkey. */ if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED || (node->pkt->pkttype == PKT_PUBLIC_KEY && gpg_err_code (err) == GPG_ERR_CANCELED)) goto leave; write_status_error ("export_keys.secret", err); skip_until_subkey = 1; err = 0; } else { if ((options & EXPORT_BACKUP)) err = build_packet_and_meta (out, node->pkt); else err = build_packet (out, node->pkt); if (node->pkt->pkttype == PKT_PUBLIC_KEY) { stats->exported++; print_status_exported (node->pkt->pkt.public_key); } } } else { log_error ("key %s: error getting keyinfo from agent: %s" " - skipped\n", keystr_with_sub (keyid, subkid), gpg_strerror (err)); skip_until_subkey = 1; err = 0; } xfree (pk->seckey_info); pk->seckey_info = NULL; { int i; for (i = pubkey_get_npkey (pk->pubkey_algo); i < pubkey_get_nskey (pk->pubkey_algo); i++) { gcry_mpi_release (pk->pkey[i]); pk->pkey[i] = NULL; } } } else /* Not secret or common packets. */ { if ((options & EXPORT_BACKUP)) err = build_packet_and_meta (out, node->pkt); else err = build_packet (out, node->pkt); if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY) { stats->exported++; print_status_exported (node->pkt->pkt.public_key); } } if (err) { log_error ("build_packet(%d) failed: %s\n", node->pkt->pkttype, gpg_strerror (err)); goto leave; } if (!skip_until_subkey) *any = 1; } leave: release_subkey_list (subkey_list); xfree (serialno); xfree (hexgrip); xfree (cache_nonce); return err; } /* Helper for do_export_stream which writes the own revocations * certificates (if any) from KEYBLOCK to OUT. */ static gpg_error_t do_export_revocs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, iobuf_t out, unsigned int options, int *any) { gpg_error_t err = 0; kbnode_t kbctx, node; PKT_signature *sig; (void)ctrl; /* NB: walk_kbnode skips packets marked as deleted. */ for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); ) { if (node->pkt->pkttype != PKT_SIGNATURE) continue; sig = node->pkt->pkt.signature; /* We are only interested in revocation certifcates. */ if (!(IS_KEY_REV (sig) || IS_UID_REV (sig) || IS_SUBKEY_REV (sig))) continue; if (!(sig->keyid[0] == keyid[0] && sig->keyid[1] == keyid[1])) continue; /* Not a self-signature. */ /* Do not export signature packets which are marked as not * exportable. */ if (!(options & EXPORT_LOCAL_SIGS) && !sig->flags.exportable) continue; /* not exportable */ /* Do not export packets with a "sensitive" revocation key * unless the user wants us to. */ if (!(options & EXPORT_SENSITIVE_REVKEYS) && sig->revkey) { int i; for (i = 0; i < sig->numrevkeys; i++) if ((sig->revkey[i].class & 0x40)) break; if (i < sig->numrevkeys) continue; } if (!sig->flags.checked) { log_info ("signature not marked as checked - ignored\n"); continue; } if (!sig->flags.valid) { log_info ("signature not not valid - ignored\n"); continue; } err = build_packet (out, node->pkt); if (err) { log_error ("build_packet(%d) failed: %s\n", node->pkt->pkttype, gpg_strerror (err)); goto leave; } *any = 1; } leave: return err; } /* For secret key export we need to setup a decryption context. * Returns 0 and the context at r_cipherhd. */ static gpg_error_t get_keywrap_key (ctrl_t ctrl, gcry_cipher_hd_t *r_cipherhd) { #ifdef ENABLE_SELINUX_HACKS (void)ctrl; *r_cipherhd = NULL; log_error (_("exporting secret keys not allowed\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); #else gpg_error_t err; void *kek = NULL; size_t keklen; gcry_cipher_hd_t cipherhd; *r_cipherhd = NULL; err = agent_keywrap_key (ctrl, 1, &kek, &keklen); if (err) { log_error ("error getting the KEK: %s\n", gpg_strerror (err)); return err; } err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (!err) err = gcry_cipher_setkey (cipherhd, kek, keklen); if (err) log_error ("error setting up an encryption context: %s\n", gpg_strerror (err)); if (!err) *r_cipherhd = cipherhd; else gcry_cipher_close (cipherhd); xfree (kek); return err; #endif } /* Export the keys identified by the list of strings in USERS to the stream OUT. If SECRET is false public keys will be exported. With secret true secret keys will be exported; in this case 1 means the entire secret keyblock and 2 only the subkeys. OPTIONS are the export options to apply. If KEYBLOCK_OUT is not NULL, AND the exit code is zero, a pointer to the first keyblock found and exported will be stored at this address; no other keyblocks are exported in this case. The caller must free the returned keyblock. If any key has been exported true is stored at ANY. */ static int do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret, kbnode_t *keyblock_out, unsigned int options, export_stats_t stats, int *any) { gpg_error_t err = 0; PACKET pkt; kbnode_t keyblock = NULL; kbnode_t node; size_t ndesc, descindex; KEYDB_SEARCH_DESC *desc = NULL; KEYDB_HANDLE kdbhd; strlist_t sl; gcry_cipher_hd_t cipherhd = NULL; struct export_stats_s dummystats; iobuf_t out_help = NULL; if (!stats) stats = &dummystats; *any = 0; init_packet (&pkt); kdbhd = keydb_new (ctrl); if (!kdbhd) return gpg_error_from_syserror (); /* For the DANE format open a helper iobuf and * enforce some options. */ if ((options & EXPORT_DANE_FORMAT)) { out_help = iobuf_temp (); options |= EXPORT_MINIMAL | EXPORT_CLEAN; } if (!users) { ndesc = 1; desc = xcalloc (ndesc, sizeof *desc); desc[0].mode = KEYDB_SEARCH_MODE_FIRST; } else { for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++) ; desc = xmalloc ( ndesc * sizeof *desc); for (ndesc=0, sl=users; sl; sl = sl->next) { if (!(err=classify_user_id (sl->d, desc+ndesc, 1))) ndesc++; else log_error (_("key \"%s\" not found: %s\n"), sl->d, gpg_strerror (err)); } keydb_disable_caching (kdbhd); /* We are looping the search. */ /* It would be nice to see which of the given users did actually match one in the keyring. To implement this we need to have a found flag for each entry in desc. To set this flag we must check all those entries after a match to mark all matched one - currently we stop at the first match. To do this we need an extra flag to enable this feature. */ } /* For secret key export we need to setup a decryption context. */ if (secret && (err = get_keywrap_key (ctrl, &cipherhd))) goto leave; for (;;) { u32 keyid[2]; PKT_public_key *pk; err = keydb_search (kdbhd, desc, ndesc, &descindex); if (!users) desc[0].mode = KEYDB_SEARCH_MODE_NEXT; if (err) break; /* Read the keyblock. */ release_kbnode (keyblock); keyblock = NULL; err = keydb_get_keyblock (kdbhd, &keyblock); if (err) { log_error (_("error reading keyblock: %s\n"), gpg_strerror (err)); goto leave; } node = find_kbnode (keyblock, PKT_PUBLIC_KEY); if (!node) { log_error ("public key packet not found in keyblock - skipped\n"); continue; } stats->count++; setup_main_keyids (keyblock); /* gpg_format_keydesc needs it. */ pk = node->pkt->pkt.public_key; keyid_from_pk (pk, keyid); /* If a secret key export is required we need to check whether we have a secret key at all and if so create the seckey_info structure. */ if (secret) { if (agent_probe_any_secret_key (ctrl, keyblock)) continue; /* No secret key (neither primary nor subkey). */ /* No v3 keys with GNU mode 1001. */ if (secret == 2 && pk->version == 3) { log_info (_("key %s: PGP 2.x style key - skipped\n"), keystr (keyid)); continue; } /* The agent does not yet allow export of v3 packets. It is actually questionable whether we should allow them at all. */ if (pk->version == 3) { log_info ("key %s: PGP 2.x style key (v3) export " "not yet supported - skipped\n", keystr (keyid)); continue; } stats->secret_count++; } /* Always do the cleaning on the public key part if requested. * A designated revocation is never stripped, even with * export-minimal set. */ if ((options & EXPORT_CLEAN)) { merge_keys_and_selfsig (ctrl, keyblock); clean_all_uids (ctrl, keyblock, opt.verbose, (options&EXPORT_MINIMAL), NULL, NULL); clean_all_subkeys (ctrl, keyblock, opt.verbose, (options&EXPORT_MINIMAL)? KEY_CLEAN_ALL /**/ : KEY_CLEAN_AUTHENCR, NULL, NULL); commit_kbnode (&keyblock); } else if (export_keep_uid || export_drop_subkey || export_select_filter) { /* Need to merge so that for example the "usage" property * has been setup. */ merge_keys_and_selfsig (ctrl, keyblock); } if (export_select_filter) { int selected = 0; struct impex_filter_parm_s parm; parm.ctrl = ctrl; for (parm.node = keyblock; parm.node; parm.node = parm.node->next) { if (recsel_select (export_select_filter, impex_filter_getval, &parm)) { selected = 1; break; } } if (!selected) continue; /* Skip this keyblock. */ } if (export_keep_uid) { commit_kbnode (&keyblock); apply_keep_uid_filter (ctrl, keyblock, export_keep_uid); commit_kbnode (&keyblock); } if (export_drop_subkey) { commit_kbnode (&keyblock); apply_drop_subkey_filter (ctrl, keyblock, export_drop_subkey); commit_kbnode (&keyblock); } /* And write it. */ if ((options & EXPORT_REVOCS)) err = do_export_revocs (ctrl, keyblock, keyid, out_help? out_help : out, options, any); else err = do_export_one_keyblock (ctrl, keyblock, keyid, out_help? out_help : out, secret, options, stats, any, desc, ndesc, descindex, cipherhd); if (err) break; if (keyblock_out) { *keyblock_out = keyblock; break; } if (out_help && (options & EXPORT_DANE_FORMAT)) { /* We want to write DANE records. OUT_HELP has the * keyblock and we print a record for each uid to OUT. */ const void *data; size_t datalen; iobuf_flush_temp (out_help); data = iobuf_get_temp_buffer (out_help); datalen = iobuf_get_temp_length (out_help); err = print_dane_records (out, keyblock, pk, data, datalen); if (err) goto leave; iobuf_close (out_help); out_help = iobuf_temp (); } } if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = 0; leave: iobuf_cancel (out_help); gcry_cipher_close (cipherhd); xfree(desc); keydb_release (kdbhd); if (err || !keyblock_out) release_kbnode( keyblock ); if( !*any && !opt.quiet) log_info (_("WARNING: nothing exported\n")); return err; } /* Write the uint32 VALUE to MB in networ byte order. */ static void mb_write_uint32 (membuf_t *mb, u32 value) { unsigned char buffer[4]; ulongtobuf (buffer, (ulong)value); put_membuf (mb, buffer, 4); } /* Write the byte C to MB. */ static void mb_write_uint8 (membuf_t *mb, int c) { unsigned char buf[1]; buf[0] = c; put_membuf (mb, buf, 1); } /* Simple wrapper around put_membuf. */ static void mb_write_data (membuf_t *mb, const void *data, size_t datalen) { put_membuf (mb, data, datalen); } /* Write STRING with terminating Nul to MB. */ static void mb_write_cstring (membuf_t *mb, const char *string) { put_membuf (mb, string, strlen (string)+1); } /* Write an SSH style string to MB. */ static void mb_write_string (membuf_t *mb, const void *string, size_t n) { mb_write_uint32 (mb, (u32)n); mb_write_data (mb, string, n); } /* Write an MPI as SSH style string to MB */ static void mb_write_mpi (membuf_t *mb, gcry_mpi_t mpi, int strip_prefix) { unsigned int nbits; const unsigned char *p; size_t n; if (gcry_mpi_get_flag (mpi, GCRYMPI_FLAG_OPAQUE)) { p = gcry_mpi_get_opaque (mpi, &nbits); n = (nbits + 7) / 8; if (strip_prefix && n > 1 && p[0] == 0x40) { /* We need to strip our 0x40 prefix. */ p++; n--; } mb_write_string (mb, p, n); } else { gpg_error_t err; unsigned char *buf; err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &n, mpi); if (err) set_membuf_err (mb, err); else { mb_write_data (mb, buf, n); gcry_free (buf); } } } static gpg_error_t key_to_sshblob (membuf_t *mb, const char *identifier, ...) { va_list arg_ptr; gpg_error_t err = 0; unsigned char nbuf[4]; unsigned char *buf; size_t buflen; gcry_mpi_t a; buflen = strlen (identifier); ulongtobuf (nbuf, (ulong)buflen); put_membuf (mb, nbuf, 4); put_membuf (mb, identifier, buflen); if (buflen > 11 && !memcmp (identifier, "ecdsa-sha2-", 11)) { /* Store the name of the curve taken from the identifier. */ ulongtobuf (nbuf, (ulong)(buflen - 11)); put_membuf (mb, nbuf, 4); put_membuf (mb, identifier+11, buflen - 11); } va_start (arg_ptr, identifier); while ((a = va_arg (arg_ptr, gcry_mpi_t))) { if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)) { unsigned int nbits; const unsigned char *p; p = gcry_mpi_get_opaque (a, &nbits); buflen = (nbits + 7) / 8; if (!strcmp (identifier, "ssh-ed25519") && buflen > 1 && p[0] == 0x40) { /* We need to strip our 0x40 prefix. */ put_membuf (mb, "\x00\x00\x00\x20", 4); put_membuf (mb, p+1, buflen-1); } else { unsigned char c; c = buflen >> 24; put_membuf (mb, &c, 1); c = buflen >> 16; put_membuf (mb, &c, 1); c = buflen >> 8; put_membuf (mb, &c, 1); c = buflen; put_membuf (mb, &c, 1); put_membuf (mb, p, buflen); } } else { err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a); if (err) break; put_membuf (mb, buf, buflen); gcry_free (buf); } } va_end (arg_ptr); return err; } static gpg_error_t export_one_ssh_key (estream_t fp, PKT_public_key *pk) { gpg_error_t err; const char *identifier = NULL; membuf_t mb; void *blob; size_t bloblen; init_membuf (&mb, 4096); switch (pk->pubkey_algo) { case PUBKEY_ALGO_DSA: identifier = "ssh-dss"; err = key_to_sshblob (&mb, identifier, pk->pkey[0], pk->pkey[1], pk->pkey[2], pk->pkey[3], NULL); break; case PUBKEY_ALGO_RSA: case PUBKEY_ALGO_RSA_S: identifier = "ssh-rsa"; err = key_to_sshblob (&mb, identifier, pk->pkey[1], pk->pkey[0], NULL); break; case PUBKEY_ALGO_ECDSA: { char *curveoid; const char *curve; curveoid = openpgp_oid_to_str (pk->pkey[0]); if (!curveoid) err = gpg_error_from_syserror (); else if (!(curve = openpgp_oid_to_curve (curveoid, 0))) err = gpg_error (GPG_ERR_UNKNOWN_CURVE); else { 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"; if (!identifier) err = gpg_error (GPG_ERR_UNKNOWN_CURVE); else err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); } xfree (curveoid); } break; case PUBKEY_ALGO_EDDSA: if (openpgp_oid_is_ed25519 (pk->pkey[0])) { identifier = "ssh-ed25519"; err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); } else if (openpgp_oid_is_ed448 (pk->pkey[0])) { identifier = "ssh-ed448"; err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); } else err = gpg_error (GPG_ERR_UNKNOWN_CURVE); break; case PUBKEY_ALGO_ELGAMAL_E: case PUBKEY_ALGO_ELGAMAL: err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY); break; default: err = GPG_ERR_PUBKEY_ALGO; break; } if (err) goto leave; blob = get_membuf (&mb, &bloblen); if (blob) { - struct b64state b64_state; + gpgrt_b64state_t b64_state; es_fprintf (fp, "%s ", identifier); - err = b64enc_start_es (&b64_state, fp, ""); - if (err) + b64_state = gpgrt_b64enc_start (fp, ""); + if (!b64_state) { xfree (blob); goto leave; } - err = b64enc_write (&b64_state, blob, bloblen); - b64enc_finish (&b64_state); + err = gpgrt_b64enc_write (b64_state, blob, bloblen); + gpgrt_b64enc_finish (b64_state); es_fprintf (fp, " openpgp:0x%08lX\n", (ulong)keyid_from_pk (pk, NULL)); xfree (blob); } leave: xfree (get_membuf (&mb, NULL)); return err; } /* Export the key identified by USERID in the SSH public key format. The function exports the latest subkey with Authentication capability unless the '!' suffix is used to export a specific key. */ gpg_error_t export_ssh_key (ctrl_t ctrl, const char *userid) { gpg_error_t err; kbnode_t keyblock = NULL; KEYDB_SEARCH_DESC desc; u32 latest_date; u32 curtime = make_timestamp (); kbnode_t latest_key, node; PKT_public_key *pk; estream_t fp = NULL; const char *fname = "-"; /* We need to know whether the key has been specified using the exact syntax ('!' suffix). Thus we need to run a classify_user_id on our own. */ err = classify_user_id (userid, &desc, 1); /* Get the public key. */ if (!err) { getkey_ctx_t getkeyctx; err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, &getkeyctx, NULL, userid, &keyblock, NULL, 0 /* Only usable keys or given exact. */); if (!err) { err = getkey_next (ctrl, getkeyctx, NULL, NULL); if (!err) err = gpg_error (GPG_ERR_AMBIGUOUS_NAME); else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) err = 0; } getkey_end (ctrl, getkeyctx); } if (err) { log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err)); return err; } /* The finish_lookup code in getkey.c does not handle auth keys, thus we have to duplicate the code here to find the latest subkey. However, if the key has been found using an exact match ('!' notation) we use that key without any further checks and even allow the use of the primary key. */ latest_date = 0; latest_key = NULL; for (node = keyblock; node; node = node->next) { if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_PUBLIC_KEY) && node->pkt->pkt.public_key->flags.exact) { latest_key = node; break; } } if (!latest_key) { for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY) continue; pk = node->pkt->pkt.public_key; if (DBG_LOOKUP) log_debug ("\tchecking subkey %08lX\n", (ulong) keyid_from_pk (pk, NULL)); if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH)) { if (DBG_LOOKUP) log_debug ("\tsubkey not usable for authentication\n"); continue; } if (!pk->flags.valid) { if (DBG_LOOKUP) log_debug ("\tsubkey not valid\n"); continue; } if (pk->flags.revoked) { if (DBG_LOOKUP) log_debug ("\tsubkey has been revoked\n"); continue; } if (pk->has_expired) { if (DBG_LOOKUP) log_debug ("\tsubkey has expired\n"); continue; } if (pk->timestamp > curtime && !opt.ignore_valid_from) { if (DBG_LOOKUP) log_debug ("\tsubkey not yet valid\n"); continue; } if (DBG_LOOKUP) log_debug ("\tsubkey might be fine\n"); /* In case a key has a timestamp of 0 set, we make sure that it is used. A better change would be to compare ">=" but that might also change the selected keys and is as such a more intrusive change. */ if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date)) { latest_date = pk->timestamp; latest_key = node; } } /* If no subkey was suitable check the primary key. */ if (!latest_key && (node = keyblock) && node->pkt->pkttype == PKT_PUBLIC_KEY) { pk = node->pkt->pkt.public_key; if (DBG_LOOKUP) log_debug ("\tchecking primary key %08lX\n", (ulong) keyid_from_pk (pk, NULL)); if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH)) { if (DBG_LOOKUP) log_debug ("\tprimary key not usable for authentication\n"); } else if (!pk->flags.valid) { if (DBG_LOOKUP) log_debug ("\tprimary key not valid\n"); } else if (pk->flags.revoked) { if (DBG_LOOKUP) log_debug ("\tprimary key has been revoked\n"); } else if (pk->has_expired) { if (DBG_LOOKUP) log_debug ("\tprimary key has expired\n"); } else if (pk->timestamp > curtime && !opt.ignore_valid_from) { if (DBG_LOOKUP) log_debug ("\tprimary key not yet valid\n"); } else { if (DBG_LOOKUP) log_debug ("\tprimary key is fine\n"); latest_date = pk->timestamp; latest_key = node; } } } if (!latest_key) { err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY); log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err)); goto leave; } pk = latest_key->pkt->pkt.public_key; if (DBG_LOOKUP) log_debug ("\tusing key %08lX\n", (ulong) keyid_from_pk (pk, NULL)); if (opt.outfile && *opt.outfile && strcmp (opt.outfile, "-")) fp = es_fopen ((fname = opt.outfile), "w"); else fp = es_stdout; if (!fp) { err = gpg_error_from_syserror (); log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } err = export_one_ssh_key (fp, pk); if (err) goto leave; if (es_ferror (fp)) err = gpg_error_from_syserror (); else { if (fp != es_stdout && es_fclose (fp)) err = gpg_error_from_syserror (); fp = NULL; } if (err) log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); leave: if (fp != es_stdout) es_fclose (fp); release_kbnode (keyblock); return err; } /* Export the key identified by USERID in the SSH secret key format. * The USERID must be given in keygrip format (prefixed with a '&') * and thus no OpenPGP key is required. The exported key is not * protected. */ gpg_error_t export_secret_ssh_key (ctrl_t ctrl, const char *userid) { gpg_error_t err; KEYDB_SEARCH_DESC desc; estream_t fp = NULL; const char *fname = "-"; gcry_cipher_hd_t cipherhd = NULL; char hexgrip[KEYGRIP_LEN * 2 + 1]; gcry_sexp_t skey = NULL; gcry_sexp_t skeyalgo = NULL; const char *identifier = NULL; membuf_t mb; membuf_t mb2; void *blob = NULL; size_t bloblen; const char *s; size_t n; char *p; int pkalgo; int i; gcry_mpi_t keyparam[10] = { NULL }; - struct b64state b64_state; + gpgrt_b64state_t b64_state; init_membuf_secure (&mb, 1024); init_membuf_secure (&mb2, 1024); /* Check that a keygrip has been given. */ err = classify_user_id (userid, &desc, 1); if (err || desc.mode != KEYDB_SEARCH_MODE_KEYGRIP ) { log_error (_("key \"%s\" not found: %s\n"), userid, err? gpg_strerror (err) : "Not a Keygrip" ); return err; } bin2hex (desc.u.grip, KEYGRIP_LEN, hexgrip); if ((err = get_keywrap_key (ctrl, &cipherhd))) goto leave; err = receive_seckey_from_agent (ctrl, cipherhd, 0, 0, NULL, hexgrip, NULL, &skey); if (err) goto leave; /* Get the type of the key expression. */ s = gcry_sexp_nth_data (skey, 0, &n); if (!s || !(n == 11 && !memcmp (s, "private-key", 11))) { log_info ("Note: only on-disk secret keys may be exported\n"); err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } mb_write_cstring (&mb, "openssh-key-v1"); /* Auth_Magic. */ mb_write_string (&mb, "none", 4); /* ciphername */ mb_write_string (&mb, "none", 4); /* kdfname */ mb_write_uint32 (&mb, 0); /* kdfoptions */ mb_write_uint32 (&mb, 1); /* number of keys */ pkalgo = get_pk_algo_from_key (skey); switch (pkalgo) { case PUBKEY_ALGO_RSA: case PUBKEY_ALGO_RSA_S: identifier = "ssh-rsa"; err = gcry_sexp_extract_param (skey, NULL, "nedpq", &keyparam[0], &keyparam[1], &keyparam[2], &keyparam[3], &keyparam[4], NULL); if (err) goto leave; mb_write_string (&mb2, identifier, strlen (identifier)); mb_write_mpi (&mb2, keyparam[1], 0); /* e (right, e is first here) */ mb_write_mpi (&mb2, keyparam[0], 0); /* n */ /* Append public to the output block as an SSH string. */ p = get_membuf (&mb2, &n); if (!p) { err = gpg_error_from_syserror (); goto leave; } mb_write_string (&mb, p, n); xfree (p); init_membuf_secure (&mb2, 1024); mb_write_string (&mb2, identifier, strlen (identifier)); { char checkbytes[4]; gcry_create_nonce (checkbytes, sizeof checkbytes); mb_write_data (&mb2, checkbytes, sizeof checkbytes); mb_write_data (&mb2, checkbytes, sizeof checkbytes); } mb_write_mpi (&mb2, keyparam[0], 0); /* n */ mb_write_mpi (&mb2, keyparam[1], 0); /* e */ /*FIXME: Fixup u,p,q to match the OpenSSH format. */ mb_write_mpi (&mb2, keyparam[2], 0); /* d */ mb_write_mpi (&mb2, keyparam[1], 0); /* iqmp1 */ mb_write_mpi (&mb2, keyparam[3], 0); /* p */ mb_write_mpi (&mb2, keyparam[4], 0); /* q */ /* Fixme: take the comment from skey. */ mb_write_string (&mb2, "", 9); /* Pad to a blocksize of 8 (for cipher "none"). */ i = 0; while (peek_membuf (&mb2, &n) && (n % 8)) mb_write_uint8 (&mb2, ++i); /* Append encrypted block to the output as an SSH string. */ p = get_membuf (&mb2, &n); if (!p) { err = gpg_error_from_syserror (); goto leave; } mb_write_string (&mb, p, n); xfree (p); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); break; /* case PUBKEY_ALGO_ECDSA: */ /* { */ /* char *curveoid; */ /* const char *curve; */ /* curveoid = openpgp_oid_to_str (pk->pkey[0]); */ /* if (!curveoid) */ /* err = gpg_error_from_syserror (); */ /* else if (!(curve = openpgp_oid_to_curve (curveoid, 0))) */ /* err = gpg_error (GPG_ERR_UNKNOWN_CURVE); */ /* else */ /* { */ /* 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"; */ /* if (!identifier) */ /* err = gpg_error (GPG_ERR_UNKNOWN_CURVE); */ /* else */ /* err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); */ /* } */ /* xfree (curveoid); */ /* } */ /* break; */ /* case PUBKEY_ALGO_EDDSA: */ /* if (openpgp_oid_is_ed25519 (pk->pkey[0])) */ /* { */ /* identifier = "ssh-ed25519"; */ /* err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); */ /* } */ /* else if (openpgp_oid_is_ed448 (pk->pkey[0])) */ /* { */ /* identifier = "ssh-ed448"; */ /* err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); */ /* } */ /* else */ /* err = gpg_error (GPG_ERR_UNKNOWN_CURVE); */ /* break; */ case PUBKEY_ALGO_DSA: log_info ("Note: export of ssh-dsa keys is not supported\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); break; case PUBKEY_ALGO_ELGAMAL_E: case PUBKEY_ALGO_ELGAMAL: err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); break; default: err = GPG_ERR_PUBKEY_ALGO; break; } if (err) goto leave; blob = get_membuf (&mb, &bloblen); if (!blob) { err = gpg_error_from_syserror (); goto leave; } if (opt.outfile && *opt.outfile && strcmp (opt.outfile, "-")) fp = es_fopen ((fname = opt.outfile), "w"); else fp = es_stdout; if (!fp) { err = gpg_error_from_syserror (); log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } - err = b64enc_start_es (&b64_state, fp, "OPENSSH PRIVATE_KEY"); - if (err) + b64_state = gpgrt_b64enc_start (fp, "OPENSSH PRIVATE_KEY"); + if (!b64_state) goto leave; - err = b64enc_write (&b64_state, blob, bloblen); - b64enc_finish (&b64_state); + err = gpgrt_b64enc_write (b64_state, blob, bloblen); + gpgrt_b64enc_finish (b64_state); if (err) goto leave; if (es_ferror (fp)) err = gpg_error_from_syserror (); else { if (fp != es_stdout && es_fclose (fp)) err = gpg_error_from_syserror (); fp = NULL; } log_info ("Beware: the private key is not protected;" " use \"ssh-keygen -p\" to protect it\n"); if (err) log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); leave: xfree (blob); gcry_sexp_release (skey); gcry_sexp_release (skeyalgo); gcry_cipher_close (cipherhd); xfree (get_membuf (&mb2, NULL)); xfree (get_membuf (&mb, NULL)); if (fp != es_stdout) es_fclose (fp); return err; } diff --git a/tools/gpg-card.c b/tools/gpg-card.c index 4002cc185..12b4be25f 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -1,4254 +1,4256 @@ /* gpg-card.c - An interactive tool to work with cards. * Copyright (C) 2019--2022 g10 Code GmbH * * This file is part of GnuPG. * * This file 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. * * 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 Lesser 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #ifdef HAVE_LIBREADLINE # define GNUPG_LIBREADLINE_H_INCLUDED # include #endif /*HAVE_LIBREADLINE*/ #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/init.h" #include "../common/sysutils.h" #include "../common/asshelp.h" #include "../common/userids.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/exechelp.h" #include "../common/ttyio.h" #include "../common/server-help.h" #include "../common/openpgpdefs.h" #include "../common/tlv.h" #include "../common/comopt.h" #include "gpg-card.h" #define CONTROL_D ('D' - 'A' + 1) #define HISTORYNAME ".gpg-card_history" /* Constants to identify the commands and options. */ enum opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oDebug = 500, oGpgProgram, oGpgsmProgram, oStatusFD, oWithColons, oNoAutostart, oAgentProgram, oDisplay, oTTYname, oTTYtype, oXauthority, oLCctype, oLCmessages, oNoKeyLookup, oNoHistory, oChUid, oDummy }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages","@"), ARGPARSE_s_n (oNoKeyLookup,"no-key-lookup", "use --no-key-lookup for \"list\""), ARGPARSE_s_n (oNoHistory,"no-history", "do not use the command history file"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* An object to create lists of labels and keyrefs. */ struct keyinfolabel_s { const char *label; const char *keyref; }; typedef struct keyinfolabel_s *keyinfolabel_t; /* Helper for --chuid. */ static const char *changeuser; /* Limit of size of data we read from a file for certain commands. */ #define MAX_GET_DATA_FROM_FILE 16384 /* Constants for OpenPGP cards. */ #define OPENPGP_USER_PIN_DEFAULT "123456" #define OPENPGP_ADMIN_PIN_DEFAULT "12345678" #define OPENPGP_KDF_DATA_LENGTH_MIN 90 #define OPENPGP_KDF_DATA_LENGTH_MAX 110 /* Local prototypes. */ static void show_keysize_warning (void); static gpg_error_t dispatch_command (card_info_t info, const char *command); static void interactive_loop (void); #ifdef HAVE_LIBREADLINE static char **command_completion (const char *text, int start, int end); #endif /*HAVE_LIBREADLINE*/ /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "gpg-card"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-card" " [options] [{[--] command [args]}] (-h for help)"); break; case 41: p = ("Syntax: gpg-card" " [options] [command [args] {-- command [args]}]\n\n" "Tool to manage cards and tokens. Without a command an interactive\n" "mode is used. Use command \"help\" to list all commands."); break; default: p = NULL; break; } return p; } static void set_opt_session_env (const char *name, const char *value) { gpg_error_t err; err = session_env_setenv (opt.session_env, name, value); if (err) log_fatal ("error setting session environment: %s\n", gpg_strerror (err)); } /* Command line parsing. */ static void parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts) { while (gpgrt_argparse (NULL, pargs, popts)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) { pargs->r_opt = ARGPARSE_INVALID_ARG; pargs->err = ARGPARSE_PRINT_ERROR; } break; case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break; case oAgentProgram: opt.agent_program = pargs->r.ret_str; break; case oStatusFD: gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; case oWithColons: opt.with_colons = 1; break; case oNoAutostart: opt.autostart = 0; break; case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break; case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break; case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break; case oXauthority: set_opt_session_env ("XAUTHORITY", pargs->r.ret_str); break; case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; case oNoKeyLookup: opt.no_key_lookup = 1; break; case oNoHistory: opt.no_history = 1; break; case oChUid: changeuser = pargs->r.ret_str; break; default: pargs->err = 2; break; } } } /* gpg-card main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs; char **command_list = NULL; int cmdidx; char *command; gnupg_reopen_std ("gpg-card"); gpgrt_set_strusage (my_strusage); gnupg_rl_initialize (); log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Setup default options. */ opt.autostart = 1; opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", gpg_strerror (gpg_error_from_syserror ())); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; parse_arguments (&pargs, opts); gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (changeuser && gnupg_chuid (changeuser, 0)) log_inc_errorcount (); /* Force later termination. */ if (log_get_errorcount (0)) exit (2); /* Process common component options. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); if (parse_comopt (GNUPG_MODULE_NAME_CARD, opt.debug)) { gnupg_status_printf (STATUS_FAILURE, "option-parser %u", gpg_error (GPG_ERR_GENERAL)); exit(2); } if (comopt.no_autostart) opt.autostart = 0; /* Set defaults for non given options. */ if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (!opt.gpgsm_program) opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM); /* Now build the list of commands. We guess the size of the array * by assuming each item is a complete command. Obviously this will * be rarely the case, but it is less code to allocate a possible * too large array. */ command_list = xcalloc (argc+1, sizeof *command_list); cmdidx = 0; command = NULL; while (argc) { for ( ; argc && strcmp (*argv, "--"); argc--, argv++) { if (!command) command = xstrdup (*argv); else { char *tmp = xstrconcat (command, " ", *argv, NULL); xfree (command); command = tmp; } } if (argc) { /* Skip the double dash. */ argc--; argv++; } if (command) { command_list[cmdidx++] = command; command = NULL; } } opt.interactive = !cmdidx; if (!opt.interactive) opt.no_history = 1; if (opt.interactive) { interactive_loop (); err = 0; } else { struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; err = 0; for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++) { err = dispatch_command (info, command); if (err) break; } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; /* This was a "quit". */ else if (command && !opt.quiet) log_info ("stopped at command '%s'\n", command); } flush_keyblock_cache (); if (command_list) { for (cmdidx=0; command_list[cmdidx]; cmdidx++) xfree (command_list[cmdidx]); xfree (command_list); } if (err) gnupg_status_printf (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); else gnupg_status_printf (STATUS_SUCCESS, NULL); return log_get_errorcount (0)? 1:0; } /* Return S or the string "[none]" if S is NULL. */ static GPGRT_INLINE const char * nullnone (const char *s) { return s? s: "[none]"; } /* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. * On error return an error code and stores NULL at R_BUFFER; on * success returns 0 and stores the number of bytes read at R_BUFLEN * and the address of a newly allocated buffer at R_BUFFER. A * complementary nul byte is always appended to the data but not * counted; this allows to pass NULL for R-BUFFER and consider the * returned data as a string. */ static gpg_error_t get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) { gpg_error_t err; estream_t fp; char *data; int n; *r_buffer = NULL; if (r_buflen) *r_buflen = 0; fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); return err; } data = xtrymalloc (MAX_GET_DATA_FROM_FILE); if (!data) { err = gpg_error_from_syserror (); log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err)); es_fclose (fp); return err; } n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp); es_fclose (fp); if (n < 0) { err = gpg_error_from_syserror (); tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); xfree (data); return err; } data[n] = 0; *r_buffer = data; if (r_buflen) *r_buflen = n; return 0; } /* Fixup the ENODEV error from scdaemon which we may see after * removing a card due to scdaemon scanning for readers with cards. * We also map the CAERD REMOVED error to the more useful CARD_NOT * PRESENT. */ static gpg_error_t fixup_scd_errors (gpg_error_t err) { if ((gpg_err_code (err) == GPG_ERR_ENODEV || gpg_err_code (err) == GPG_ERR_CARD_REMOVED) && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) err = gpg_error (GPG_ERR_CARD_NOT_PRESENT); return err; } /* Set the card removed flag from INFO depending on ERR. This does * not clear the flag. */ static gpg_error_t maybe_set_card_removed (card_info_t info, gpg_error_t err) { if ((gpg_err_code (err) == GPG_ERR_ENODEV || gpg_err_code (err) == GPG_ERR_CARD_REMOVED) && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) info->card_removed = 1; return err; } /* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on * success. */ static gpg_error_t put_data_to_file (const char *fname, const void *buffer, size_t length) { gpg_error_t err; estream_t fp; fp = es_fopen (fname, "wb"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err)); return err; } if (length && es_fwrite (buffer, length, 1, fp) != 1) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); es_fclose (fp); return err; } if (es_fclose (fp)) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); return err; } return 0; } /* Return a malloced string with the number opf the menu PROMPT. * Control-D is mapped to "Q". */ static char * get_selection (const char *prompt) { char *answer; tty_printf ("\n"); tty_printf ("%s", prompt); tty_printf ("\n"); answer = tty_get (_("Your selection? ")); tty_kill_prompt (); if (*answer == CONTROL_D) strcpy (answer, "q"); return answer; } /* Simply prints TEXT to the output. Returns 0 as a convenience. * This is a separate function so that it can be extended to run * less(1) or so. The extra arguments are int values terminated by a * 0 to indicate card application types supported with this command. * If none are given (just the final 0), this is a general * command. */ static gpg_error_t print_help (const char *text, ...) { estream_t fp; va_list arg_ptr; int value; int any = 0; fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "%s\n", text); va_start (arg_ptr, text); while ((value = va_arg (arg_ptr, int))) { if (!any) tty_fprintf (fp, "[Supported by: "); tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value)); any = 1; } if (any) tty_fprintf (fp, "]\n"); va_end (arg_ptr); return 0; } /* Print an (OpenPGP) fingerprint. */ static void print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen) { int i; if (fpr) { for (i=0; i < fprlen ; i++, fpr++) tty_fprintf (fp, "%02X", *fpr); } else tty_fprintf (fp, " [none]"); tty_fprintf (fp, "\n"); } /* Print the keygrip GRP. */ static void print_keygrip (estream_t fp, const unsigned char *grp) { int i; for (i=0; i < 20 ; i++, grp++) tty_fprintf (fp, "%02X", *grp); tty_fprintf (fp, "\n"); } /* Print a string but avoid printing control characters. */ static void print_string (estream_t fp, const char *text, const char *name) { tty_fprintf (fp, "%s", text); /* FIXME: tty_printf_utf8_string2 eats everything after and including an @ - e.g. when printing an url. */ if (name && *name) { if (fp) print_utf8_buffer2 (fp, name, strlen (name), '\n'); else tty_print_utf8_string2 (NULL, name, strlen (name), 0); } else tty_fprintf (fp, _("[not set]")); tty_fprintf (fp, "\n"); } /* Print an ISO formatted name or "[not set]". */ static void print_isoname (estream_t fp, const char *name) { if (name && *name) { char *p, *given, *buf; buf = xstrdup (name); given = strstr (buf, "<<"); for (p=buf; *p; p++) if (*p == '<') *p = ' '; if (given && given[2]) { *given = 0; given += 2; if (fp) print_utf8_buffer2 (fp, given, strlen (given), '\n'); else tty_print_utf8_string2 (NULL, given, strlen (given), 0); if (*buf) tty_fprintf (fp, " "); } if (fp) print_utf8_buffer2 (fp, buf, strlen (buf), '\n'); else tty_print_utf8_string2 (NULL, buf, strlen (buf), 0); xfree (buf); } else { tty_fprintf (fp, _("[not set]")); } tty_fprintf (fp, "\n"); } /* Return true if the buffer MEM of length memlen consists only of zeroes. */ static int mem_is_zero (const char *mem, unsigned int memlen) { int i; for (i=0; i < memlen && !mem[i]; i++) ; return (i == memlen); } /* Helper to list a single keyref. LABEL_KEYREF is a fallback key * reference if no info is available; it may be NULL. */ static void list_one_kinfo (card_info_t info, key_info_t kinfo, const char *label_keyref, estream_t fp, int no_key_lookup, int create_shadow) { gpg_error_t err; key_info_t firstkinfo = info->kinfo; keyblock_t keyblock = NULL; keyblock_t kb; pubkey_t pubkey; userid_t uid; key_info_t ki; const char *s; gcry_sexp_t s_pkey; int any; if (firstkinfo && kinfo) { tty_fprintf (fp, " "); if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) { tty_fprintf (fp, "[none]\n"); tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref); if (kinfo->label) tty_fprintf (fp, " label ......: %s\n", kinfo->label); tty_fprintf (fp, " algorithm ..: %s\n", nullnone (kinfo->keyalgo)); goto leave; } print_keygrip (fp, kinfo->grip); tty_fprintf (fp, " keyref .....: %s", kinfo->keyref); if (kinfo->usage) { any = 0; tty_fprintf (fp, " ("); if ((kinfo->usage & GCRY_PK_USAGE_SIGN)) { tty_fprintf (fp, "sign"); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_CERT)) { tty_fprintf (fp, "%scert", any?",":""); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_AUTH)) { tty_fprintf (fp, "%sauth", any?",":""); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_ENCR)) { tty_fprintf (fp, "%sencr", any?",":""); any=1; } tty_fprintf (fp, ")"); } tty_fprintf (fp, "\n"); if (kinfo->label) tty_fprintf (fp, " label ......: %s\n", kinfo->label); if (!(err = scd_readkey (kinfo->keyref, create_shadow, &s_pkey))) { char *tmp = pubkey_algo_string (s_pkey, NULL); tty_fprintf (fp, " algorithm ..: %s\n", nullnone (tmp)); xfree (tmp); gcry_sexp_release (s_pkey); s_pkey = NULL; } else { maybe_set_card_removed (info, err); tty_fprintf (fp, " algorithm ..: %s\n", nullnone (kinfo->keyalgo)); } if (kinfo->fprlen && kinfo->created) { tty_fprintf (fp, " stored fpr .: "); print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen); tty_fprintf (fp, " created ....: %s\n", isotimestamp (kinfo->created)); } if (no_key_lookup) err = 0; else err = get_matching_keys (kinfo->grip, (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS), &keyblock); if (err) { if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY) tty_fprintf (fp, " used for ...: [%s]\n", gpg_strerror (err)); goto leave; } for (kb = keyblock; kb; kb = kb->next) { tty_fprintf (fp, " used for ...: %s\n", kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" : kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?"); pubkey = kb->keys; if (kb->protocol == GNUPG_PROTOCOL_OPENPGP) { /* If this is not the primary key print the primary * key's fingerprint or a reference to it. */ tty_fprintf (fp, " main key .: "); for (ki=firstkinfo; ki; ki = ki->next) if (pubkey->grip_valid && !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN)) break; if (ki) { /* Fixme: Replace mapping by a table lookup. */ if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) s = "this"; else if (!strcmp (ki->keyref, "OPENPGP.1")) s = "Signature key"; else if (!strcmp (ki->keyref, "OPENPGP.2")) s = "Encryption key"; else if (!strcmp (ki->keyref, "OPENPGP.3")) s = "Authentication key"; else s = NULL; if (s) tty_fprintf (fp, "<%s>\n", s); else tty_fprintf (fp, "\n", ki->keyref); } else /* Print the primary key as fallback. */ print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); } if (kb->protocol == GNUPG_PROTOCOL_OPENPGP || kb->protocol == GNUPG_PROTOCOL_CMS) { /* Find the primary or subkey of that key. */ for (; pubkey; pubkey = pubkey->next) if (pubkey->grip_valid && !memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) break; if (pubkey) { tty_fprintf (fp, " fpr ......: "); print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); tty_fprintf (fp, " created ..: %s\n", isotimestamp (pubkey->created)); } } for (uid = kb->uids; uid; uid = uid->next) { print_string (fp, " user id ..: ", uid->value); } } } else { tty_fprintf (fp, " [none]\n"); if (label_keyref) tty_fprintf (fp, " keyref .....: %s\n", label_keyref); if (kinfo) tty_fprintf (fp, " algorithm ..: %s\n", nullnone (kinfo->keyalgo)); } leave: release_keyblock (keyblock); } /* List all keyinfo in INFO using the list of LABELS. */ static void list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp, int no_key_lookup, int create_shadow) { key_info_t kinfo; int idx, i, j; /* Print the keyinfo. We first print those we known and then all * remaining item. */ for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) kinfo->xflag = 0; if (labels) { for (idx=0; labels[idx].label; idx++) { tty_fprintf (fp, "%s", labels[idx].label); kinfo = find_kinfo (info, labels[idx].keyref); list_one_kinfo (info, kinfo, labels[idx].keyref, fp, no_key_lookup, create_shadow); if (kinfo) kinfo->xflag = 1; } } for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) { if (kinfo->xflag) continue; tty_fprintf (fp, "Key %s", kinfo->keyref); for (i=4+strlen (kinfo->keyref), j=0; i < 18; i++, j=1) tty_fprintf (fp, j? ".":" "); tty_fprintf (fp, ":"); list_one_kinfo (info, kinfo, NULL, fp, no_key_lookup, create_shadow); } } static void list_retry_counter (card_info_t info, estream_t fp) { const char *s; int i; if (info->chvlabels) tty_fprintf (fp, "PIN labels .......: %s\n", info->chvlabels); tty_fprintf (fp, "PIN retry counter :"); for (i=0; i < DIM (info->chvinfo) && i < info->nchvinfo; i++) { if (info->chvinfo[i] >= 0) tty_fprintf (fp, " %d", info->chvinfo[i]); else { switch (info->chvinfo[i]) { case -1: s = "[error]"; break; case -2: s = "-"; break; /* No such PIN or info not available. */ case -3: s = "[blocked]"; break; case -4: s = "[nullpin]"; break; case -5: s = "[verified]"; break; default: s = "[?]"; break; } tty_fprintf (fp, " %s", s); } } tty_fprintf (fp, "\n"); } /* List OpenPGP card specific data. */ static void list_openpgp (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { "Signature key ....:", "OPENPGP.1" }, { "Encryption key....:", "OPENPGP.2" }, { "Authentication key:", "OPENPGP.3" }, { NULL, NULL } }; if (info->apptype != APP_TYPE_OPENPGP) { tty_fprintf (fp, "invalid OpenPGP card\n"); return; } tty_fprintf (fp, "Name of cardholder: "); print_isoname (fp, info->disp_name); print_string (fp, "Language prefs ...: ", info->disp_lang); tty_fprintf (fp, "Salutation .......: %s\n", info->disp_sex == 1? _("Mr."): info->disp_sex == 2? _("Ms.") : ""); print_string (fp, "URL of public key : ", info->pubkey_url); print_string (fp, "Login data .......: ", info->login_data); if (info->private_do[0]) print_string (fp, "Private DO 1 .....: ", info->private_do[0]); if (info->private_do[1]) print_string (fp, "Private DO 2 .....: ", info->private_do[1]); if (info->private_do[2]) print_string (fp, "Private DO 3 .....: ", info->private_do[2]); if (info->private_do[3]) print_string (fp, "Private DO 4 .....: ", info->private_do[3]); if (info->cafpr1len) { tty_fprintf (fp, "CA fingerprint %d .:", 1); print_shax_fpr (fp, info->cafpr1, info->cafpr1len); } if (info->cafpr2len) { tty_fprintf (fp, "CA fingerprint %d .:", 2); print_shax_fpr (fp, info->cafpr2, info->cafpr2len); } if (info->cafpr3len) { tty_fprintf (fp, "CA fingerprint %d .:", 3); print_shax_fpr (fp, info->cafpr3, info->cafpr3len); } tty_fprintf (fp, "Signature PIN ....: %s\n", info->chv1_cached? _("not forced"): _("forced")); tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]); list_retry_counter (info, fp); tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter); tty_fprintf (fp, "Capabilities .....:"); if (info->extcap.ki) tty_fprintf (fp, " key-import"); if (info->extcap.aac) tty_fprintf (fp, " algo-change"); if (info->extcap.bt) tty_fprintf (fp, " button"); if (info->extcap.sm) tty_fprintf (fp, " sm(%s)", gcry_cipher_algo_name (info->extcap.smalgo)); if (info->extcap.private_dos) tty_fprintf (fp, " priv-data"); tty_fprintf (fp, "\n"); if (info->extcap.kdf) { tty_fprintf (fp, "KDF setting ......: %s\n", info->kdf_do_enabled ? "on" : "off"); } if (info->extcap.bt) { tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", info->uif[0] ? (info->uif[0]==2? "permanent": "on") : "off", info->uif[1] ? (info->uif[0]==2? "permanent": "on") : "off", info->uif[2] ? (info->uif[0]==2? "permanent": "on") : "off"); } list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } /* List PIV card specific data. */ static void list_piv (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { "PIV authentication:", "PIV.9A" }, { "Card authenticat. :", "PIV.9E" }, { "Digital signature :", "PIV.9C" }, { "Key management ...:", "PIV.9D" }, { NULL, NULL } }; if (info->chvusage[0] || info->chvusage[1]) { tty_fprintf (fp, "PIN usage policy .:"); if ((info->chvusage[0] & 0x40)) tty_fprintf (fp, " app-pin"); if ((info->chvusage[0] & 0x20)) tty_fprintf (fp, " global-pin"); if ((info->chvusage[0] & 0x10)) tty_fprintf (fp, " occ"); if ((info->chvusage[0] & 0x08)) tty_fprintf (fp, " vci"); if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04)) tty_fprintf (fp, " pairing"); if (info->chvusage[1] == 0x10) tty_fprintf (fp, " primary:card"); else if (info->chvusage[1] == 0x20) tty_fprintf (fp, " primary:global"); tty_fprintf (fp, "\n"); } list_retry_counter (info, fp); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } /* List Netkey card specific data. */ static void list_nks (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { NULL, NULL } }; list_retry_counter (info, fp); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } /* List PKCS#15 card specific data. */ static void list_p15 (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { NULL, NULL } }; list_retry_counter (info, fp); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } static void print_a_version (estream_t fp, const char *prefix, unsigned int value) { unsigned int a, b, c, d; a = ((value >> 24) & 0xff); b = ((value >> 16) & 0xff); c = ((value >> 8) & 0xff); d = ((value ) & 0xff); if (a) tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d); else if (b) tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d); else if (c) tty_fprintf (fp, "%s %u.%u\n", prefix, c, d); else tty_fprintf (fp, "%s %u\n", prefix, d); } /* Print all available information about the current card. With * NO_KEY_LOOKUP the sometimes expensive listing of all matching * OpenPGP and X.509 keys is not done */ static void list_card (card_info_t info, int no_key_lookup, int create_shadow) { estream_t fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "Reader ...........: %s\n", nullnone (info->reader)); if (info->cardtype) tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); if (info->cardversion) print_a_version (fp, "Card firmware ....:", info->cardversion); tty_fprintf (fp, "Serial number ....: %s\n", nullnone (info->serialno)); tty_fprintf (fp, "Application type .: %s%s%s%s\n", app_type_string (info->apptype), info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr ? info->apptypestr:"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":""); if (info->appversion) print_a_version (fp, "Version ..........:", info->appversion); if (info->serialno && info->dispserialno && strcmp (info->serialno, info->dispserialno)) tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno); if (info->manufacturer_name && info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: %s (%x)\n", info->manufacturer_name, info->manufacturer_id); else if (info->manufacturer_name && !info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: %s\n", info->manufacturer_name); else if (info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: (%x)\n", info->manufacturer_id); switch (info->apptype) { case APP_TYPE_OPENPGP: list_openpgp (info, fp, no_key_lookup, create_shadow); break; case APP_TYPE_PIV: list_piv (info, fp, no_key_lookup, create_shadow); break; case APP_TYPE_NKS: list_nks (info, fp, no_key_lookup, create_shadow); break; case APP_TYPE_P15: list_p15 (info, fp, no_key_lookup, create_shadow); break; default: break; } } /* Helper for cmd_list. */ static void print_card_list (estream_t fp, card_info_t info, strlist_t cards, int only_current) { int count; strlist_t sl; size_t snlen; int star; const char *s; for (count = 0, sl = cards; sl; sl = sl->next, count++) { if (info && info->serialno) { s = strchr (sl->d, ' '); if (s) snlen = s - sl->d; else snlen = strlen (sl->d); star = (strlen (info->serialno) == snlen && !memcmp (info->serialno, sl->d, snlen)); } else star = 0; if (!only_current || star) tty_fprintf (fp, "%d%c %s\n", count, star? '*':' ', sl->d); } } /* The LIST command. This also updates INFO if needed. */ static gpg_error_t cmd_list (card_info_t info, char *argstr) { gpg_error_t err; int opt_cards, opt_apps, opt_info, opt_reread, opt_no_key_lookup; int opt_shadow; strlist_t cards = NULL; strlist_t sl; estream_t fp = opt.interactive? NULL : es_stdout; const char *cardsn = NULL; char *appstr = NULL; int count; int need_learn = 0; if (!info) return print_help ("LIST [--cards] [--apps] [--info] [--reread] [--shadow]" " [--no-key-lookup] [N] [APP]\n\n" "Show the content of the current card.\n" "With N given select and list the N-th card;\n" "with APP also given select that application.\n" "To select an APP on the current card use '-' for N.\n" "The S/N of the card may be used instead of N.\n" " --cards list available cards\n" " --apps list additional card applications\n" " --info select a card and prints its s/n\n" " --reread read infos from PCKS#15 cards again\n" " --shadow create shadow keys for all card keys\n" " --no-key-lookup do not list matching OpenPGP or X.509 keys\n" , 0); opt_cards = has_leading_option (argstr, "--cards"); opt_apps = has_leading_option (argstr, "--apps"); opt_info = has_leading_option (argstr, "--info"); opt_reread = has_leading_option (argstr, "--reread"); opt_shadow = has_leading_option (argstr, "--shadow"); opt_no_key_lookup = has_leading_option (argstr, "--no-key-lookup"); argstr = skip_options (argstr); if (opt_shadow) opt_no_key_lookup = 1; if (opt.no_key_lookup) opt_no_key_lookup = 1; if (hexdigitp (argstr) || (*argstr == '-' && spacep (argstr+1))) { if (*argstr == '-' && (argstr[1] || spacep (argstr+1))) argstr++; /* Keep current card. */ else { cardsn = argstr; while (hexdigitp (argstr)) argstr++; if (*argstr && !spacep (argstr)) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (*argstr) *argstr++ = 0; } while (spacep (argstr)) argstr++; if (*argstr) { appstr = argstr; while (*argstr && !spacep (argstr)) argstr++; while (spacep (argstr)) argstr++; if (*argstr) { /* Extra arguments found. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } } } else if (*argstr) { /* First argument needs to be a digit. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (!info->serialno || info->need_sn_cmd) { /* This is probably the first call or was explicitly requested. * We need to send a SERIALNO command to scdaemon so that our * session knows all cards. */ err = scd_serialno (NULL, NULL); if (err) goto leave; info->need_sn_cmd = 0; need_learn = 1; } if (opt_cards || opt_apps) { /* Note that with option --apps CARDS is here the list of all * apps. Format is "SERIALNO APPNAME {APPNAME}". We print the * card number in the first column. */ if (opt_apps) err = scd_applist (&cards, opt_cards); else err = scd_cardlist (&cards); if (err) goto leave; print_card_list (fp, info, cards, 0); } else { if (cardsn) { int i, cardno; err = scd_cardlist (&cards); if (err) goto leave; /* Switch to the requested card. */ for (i=0; digitp (cardsn+i); i++) ; if (i && i < 4 && !cardsn[i]) { /* Looks like an index into the card list. */ cardno = atoi (cardsn); for (count = 0, sl = cards; sl; sl = sl->next, count++) if (count == cardno) break; if (!sl) { err = gpg_error (GPG_ERR_INV_INDEX); goto leave; } } else /* S/N of card specified. */ { for (sl = cards; sl; sl = sl->next) if (!ascii_strcasecmp (sl->d, cardsn)) break; if (!sl) { err = gpg_error (GPG_ERR_INV_INDEX); goto leave; } } err = scd_switchcard (sl->d); need_learn = 1; } else /* show app list. */ { err = scd_applist (&cards, 1); if (err) goto leave; } if (appstr && *appstr) { /* Switch to the requested app. */ err = scd_switchapp (appstr); if (err) goto leave; need_learn = 1; } if (need_learn) err = scd_learn (info, opt_reread); else err = 0; if (err) ; else if (opt_info) print_card_list (fp, info, cards, 1); else { size_t snlen; const char *s; /* First get the list of active cards and check whether the * current card is still in the list. If not the card has * been removed. Note that during the listing the card * remove state might also be detected but only if an access * to the scdaemon is required; it is anyway better to test * that before starting a listing. */ free_strlist (cards); err = scd_cardlist (&cards); if (err) goto leave; for (sl = cards; sl; sl = sl->next) { if (info && info->serialno) { s = strchr (sl->d, ' '); if (s) snlen = s - sl->d; else snlen = strlen (sl->d); if (strlen (info->serialno) == snlen && !memcmp (info->serialno, sl->d, snlen)) break; } } if (!sl) { info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); goto leave; } list_card (info, opt_no_key_lookup, opt_shadow); } } leave: free_strlist (cards); return err; } /* The VERIFY command. */ static gpg_error_t cmd_verify (card_info_t info, char *argstr) { gpg_error_t err, err2; const char *pinref; if (!info) return print_help ("verify [chvid]", 0); if (*argstr) pinref = argstr; else if (info->apptype == APP_TYPE_OPENPGP) pinref = info->serialno; else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else return gpg_error (GPG_ERR_MISSING_VALUE); err = scd_checkpin (pinref); if (err) log_error ("verify failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); /* In any case update the CHV status, so that the next "list" shows * the correct retry counter values. */ err2 = scd_getattr ("CHV-STATUS", info); return err ? err : err2; } static gpg_error_t cmd_authenticate (card_info_t info, char *argstr) { gpg_error_t err; int opt_setkey; int opt_raw; char *string = NULL; char *key = NULL; size_t keylen; if (!info) return print_help ("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n" "Perform a mutual authentication either by reading the key\n" "from FILE or by taking it from the command line. Without\n" "the option --raw the key is expected to be hex encoded.\n" "To install a new administration key --setkey is used; this\n" "requires a prior authentication with the old key.", APP_TYPE_PIV, 0); if (info->apptype != APP_TYPE_PIV) { log_info ("Note: This is a PIV only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } opt_setkey = has_leading_option (argstr, "--setkey"); opt_raw = has_leading_option (argstr, "--raw"); argstr = skip_options (argstr); if (*argstr == '<') /* Read key from a file. */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &string, NULL); if (err) goto leave; } if (opt_raw) { key = string? string : xstrdup (argstr); string = NULL; keylen = strlen (key); } else { key = hex_to_buffer (string? string: argstr, &keylen); if (!key) { err = gpg_error_from_syserror (); goto leave; } } err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen); leave: if (key) { wipememory (key, keylen); xfree (key); } xfree (string); return err; } /* Helper for cmd_name to qyery a part of name. */ static char * ask_one_name (const char *prompt) { char *name; int i; for (;;) { name = tty_get (prompt); trim_spaces (name); tty_kill_prompt (); if (!*name || *name == CONTROL_D) { if (*name == CONTROL_D) tty_fprintf (NULL, "\n"); xfree (name); return NULL; } for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++) ; /* The name must be in Latin-1 and not UTF-8 - lacking the code * to ensure this we restrict it to ASCII. */ if (name[i]) tty_printf (_("Error: Only plain ASCII is currently allowed.\n")); else if (strchr (name, '<')) tty_printf (_("Error: The \"<\" character may not be used.\n")); else if (strstr (name, " ")) tty_printf (_("Error: Double spaces are not allowed.\n")); else return name; xfree (name); } } /* The NAME command. */ static gpg_error_t cmd_name (card_info_t info, const char *argstr) { gpg_error_t err; char *surname, *givenname; char *isoname, *p; if (!info) return print_help ("name [--clear]\n\n" "Set the name field of an OpenPGP card. With --clear the stored\n" "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } again: if (!strcmp (argstr, "--clear")) isoname = xstrdup (" "); /* No real way to clear; set to space instead. */ else { surname = ask_one_name (_("Cardholder's surname: ")); givenname = ask_one_name (_("Cardholder's given name: ")); if (!surname || !givenname || (!*surname && !*givenname)) { xfree (surname); xfree (givenname); return gpg_error (GPG_ERR_CANCELED); } isoname = xstrconcat (surname, "<<", givenname, NULL); xfree (surname); xfree (givenname); for (p=isoname; *p; p++) if (*p == ' ') *p = '<'; if (strlen (isoname) > 39 ) { log_info (_("Error: Combined name too long " "(limit is %d characters).\n"), 39); xfree (isoname); goto again; } } err = scd_setattr ("DISP-NAME", isoname, strlen (isoname)); xfree (isoname); return err; } static gpg_error_t cmd_url (card_info_t info, const char *argstr) { gpg_error_t err; char *url; if (!info) return print_help ("URL [--clear]\n\n" "Set the URL data object. That data object can be used by\n" "the FETCH command to retrieve the full public key. The\n" "option --clear deletes the content of that data object.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!strcmp (argstr, "--clear")) url = xstrdup (" "); /* No real way to clear; set to space instead. */ else { url = tty_get (_("URL to retrieve public key: ")); trim_spaces (url); tty_kill_prompt (); if (!*url || *url == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } err = scd_setattr ("PUBKEY-URL", url, strlen (url)); leave: xfree (url); return err; } /* Fetch the key from the URL given on the card or try to get it from * the default keyserver. */ static gpg_error_t cmd_fetch (card_info_t info) { gpg_error_t err; key_info_t kinfo; if (!info) return print_help ("FETCH\n\n" "Retrieve a key using the URL data object or if that is missing\n" "using the fingerprint.", APP_TYPE_OPENPGP, 0); if (info->pubkey_url && *info->pubkey_url) { /* strlist_t sl = NULL; */ /* add_to_strlist (&sl, info.pubkey_url); */ /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */ /* free_strlist (sl); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen) { /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */ /* opt.keyserver, 0); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } else err = gpg_error (GPG_ERR_NO_DATA); return err; } static gpg_error_t cmd_login (card_info_t info, char *argstr) { gpg_error_t err; char *data; size_t datalen; int use_default_pin; if (!info) return print_help ("LOGIN [--clear|--use-default-pin] [< FILE]\n\n" "Set the login data object. If FILE is given the data is\n" "is read from that file. This allows for binary data.\n" "The option --clear deletes the login data. --use-default-pin\n" "tells the card to always use the default PIN (\"123456\").", APP_TYPE_OPENPGP, 0); use_default_pin = has_leading_option (argstr, "--use-default-pin"); argstr = skip_options (argstr); if (!strcmp (argstr, "--clear")) { data = xstrdup (" "); /* kludge. */ datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else { data = tty_get (_("Login data (account name): ")); trim_spaces (data); tty_kill_prompt (); if ((!*data && !use_default_pin) || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } datalen = strlen (data); } if (use_default_pin) { char *tmpdata = xmalloc (datalen + 5); memcpy (tmpdata, data, datalen); memcpy (tmpdata+datalen, "\n\x14" "F=3", 5); xfree (data); data = tmpdata; datalen += 5; } err = scd_setattr ("LOGIN-DATA", data, datalen); leave: xfree (data); return err; } static gpg_error_t cmd_lang (card_info_t info, const char *argstr) { gpg_error_t err; char *data, *p; if (!info) return print_help ("LANG [--clear]\n\n" "Change the language info for the card. This info can be used\n" "by applications for a personalized greeting. Up to 4 two-digit\n" "language identifiers can be entered as a preference. The option\n" "--clear removes all identifiers. GnuPG does not use this info.", APP_TYPE_OPENPGP, 0); if (!strcmp (argstr, "--clear")) data = xstrdup (" "); /* Note that we need two spaces here. */ else { again: data = tty_get (_("Language preferences: ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (strlen (data) > 8 || (strlen (data) & 1)) { log_info (_("Error: invalid length of preference string.\n")); xfree (data); goto again; } for (p=data; *p && *p >= 'a' && *p <= 'z'; p++) ; if (*p) { log_info (_("Error: invalid characters in preference string.\n")); xfree (data); goto again; } } err = scd_setattr ("DISP-LANG", data, strlen (data)); leave: xfree (data); return err; } static gpg_error_t cmd_salut (card_info_t info, const char *argstr) { gpg_error_t err; char *data = NULL; const char *str; if (!info) return print_help ("SALUT [--clear]\n\n" "Change the salutation info for the card. This info can be used\n" "by applications for a personalized greeting. The option --clear\n" "removes this data object. GnuPG does not use this info.", APP_TYPE_OPENPGP, 0); again: if (!strcmp (argstr, "--clear")) str = "9"; else { data = tty_get (_("Salutation (M = Mr., F = Ms., or space): ")); trim_spaces (data); tty_kill_prompt (); if (*data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (!*data) str = "9"; else if ((*data == 'M' || *data == 'm') && !data[1]) str = "1"; else if ((*data == 'F' || *data == 'f') && !data[1]) str = "2"; else { tty_printf (_("Error: invalid response.\n")); xfree (data); data = NULL; goto again; } } err = scd_setattr ("DISP-SEX", str, 1); leave: xfree (data); return err; } static gpg_error_t cmd_cafpr (card_info_t info, char *argstr) { gpg_error_t err; char *data = NULL; const char *s; int i, c; unsigned char fpr[32]; int fprlen; int fprno; int opt_clear = 0; if (!info) return print_help ("CAFPR [--clear] N\n\n" "Change the CA fingerprint number N. N must be in the\n" "range 1 to 3. The option --clear clears the specified\n" "CA fingerprint N or all of them if N is 0 or not given.", APP_TYPE_OPENPGP, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); if (digitp (argstr)) { fprno = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else fprno = 0; if (opt_clear && !fprno) ; /* Okay: clear all fprs. */ else if (fprno < 1 || fprno > 3) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } again: if (opt_clear) { memset (fpr, 0, 20); fprlen = 20; } else { xfree (data); data = tty_get (_("CA fingerprint: ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } for (i=0, s=data; i < sizeof fpr && *s; ) { while (spacep(s)) s++; if (*s == ':') s++; while (spacep(s)) s++; c = hextobyte (s); if (c == -1) break; fpr[i++] = c; s += 2; } fprlen = i; if ((fprlen != 20 && fprlen != 32) || *s) { log_error (_("Error: invalid formatted fingerprint.\n")); goto again; } } if (!fprno) { log_assert (opt_clear); err = scd_setattr ("CA-FPR-1", fpr, fprlen); if (!err) err = scd_setattr ("CA-FPR-2", fpr, fprlen); if (!err) err = scd_setattr ("CA-FPR-3", fpr, fprlen); } else err = scd_setattr (fprno==1?"CA-FPR-1": fprno==2?"CA-FPR-2": fprno==3?"CA-FPR-3":"x", fpr, fprlen); leave: xfree (data); return err; } static gpg_error_t cmd_privatedo (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; char *do_name = NULL; char *data = NULL; size_t datalen; int do_no; if (!info) return print_help ("PRIVATEDO [--clear] N [< FILE]\n\n" "Change the private data object N. N must be in the\n" "range 1 to 4. If FILE is given the data is is read\n" "from that file. The option --clear clears the data.", APP_TYPE_OPENPGP, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); if (digitp (argstr)) { do_no = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else do_no = 0; if (do_no < 1 || do_no > 4) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } do_name = xasprintf ("PRIVATE-DO-%d", do_no); if (opt_clear) { data = xstrdup (" "); datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else if (*argstr) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } else { data = tty_get (_("Private DO data: ")); trim_spaces (data); tty_kill_prompt (); datalen = strlen (data); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } err = scd_setattr (do_name, data, datalen); leave: xfree (do_name); xfree (data); return err; } static gpg_error_t cmd_writecert (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; int opt_openpgp; char *certref_buffer = NULL; char *certref; char *data = NULL; size_t datalen; estream_t key = NULL; if (!info) return print_help ("WRITECERT CERTREF '<' FILE\n" "WRITECERT --openpgp CERTREF ['<' FILE|FPR]\n" "WRITECERT --clear CERTREF\n\n" "Write a certificate to the card under the id CERTREF.\n" "The option --clear removes the certificate from the card.\n" "The option --openpgp expects an OpenPGP keyblock and stores\n" "it encapsulated in a CMS container; the keyblock is taken\n" "from FILE or directly from the OpenPGP key with FPR", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_clear = has_leading_option (argstr, "--clear"); opt_openpgp = has_leading_option (argstr, "--openpgp"); argstr = skip_options (argstr); certref = argstr; if ((argstr = strchr (certref, ' '))) { *argstr++ = 0; trim_spaces (certref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = certref + strlen (certref); if (info->apptype == APP_TYPE_OPENPGP) { if (!ascii_strcasecmp (certref, "OPENPGP.3") || !strcmp (certref, "3")) certref_buffer = xstrdup ("OPENPGP.3"); else if (!ascii_strcasecmp (certref, "OPENPGP.2")||!strcmp (certref,"2")) certref_buffer = xstrdup ("OPENPGP.2"); else if (!ascii_strcasecmp (certref, "OPENPGP.1")||!strcmp (certref,"1")) certref_buffer = xstrdup ("OPENPGP.1"); else { err = gpg_error (GPG_ERR_INV_ID); log_error ("Error: CERTREF must be OPENPGP.N or just N" " with N being 1..3\""); goto leave; } certref = certref_buffer; } else /* Upcase the certref; prepend cardtype if needed. */ { if (!strchr (certref, '.')) certref_buffer = xstrconcat (app_type_string (info->apptype), ".", certref, NULL); else certref_buffer = xstrdup (certref); ascii_strupr (certref_buffer); certref = certref_buffer; } if (opt_clear) { data = xstrdup (" "); datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----") && ascii_memistr (data, datalen, "-----END CERTIFICATE-----") && !memchr (data, 0, datalen) && !memchr (data, 1, datalen)) { - struct b64state b64; + gpgrt_b64state_t b64; - err = b64dec_start (&b64, ""); - if (!err) - err = b64dec_proc (&b64, data, datalen, &datalen); + b64 = gpgrt_b64dec_start (""); + if (!b64) + err = gpg_error_from_syserror (); + else + err = gpgrt_b64dec_proc (b64, data, datalen, &datalen); if (!err) - err = b64dec_finish (&b64); + err = gpgrt_b64dec_finish (b64); if (err) goto leave; } } else if (opt_openpgp && *argstr) { err = get_minimal_openpgp_key (&key, argstr); if (err) goto leave; if (es_fclose_snatch (key, (void*)&data, &datalen)) { err = gpg_error_from_syserror (); goto leave; } key = NULL; } else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (opt_openpgp && !opt_clear) { tlv_builder_t tb; void *tmpder; size_t tmpderlen; tb = tlv_builder_new (0); if (!tb) { err = gpg_error_from_syserror (); goto leave; } tlv_builder_add_tag (tb, 0, TAG_SEQUENCE); tlv_builder_add_ptr (tb, 0, TAG_OBJECT_ID, "\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", 10); tlv_builder_add_tag (tb, CLASS_CONTEXT, 0); tlv_builder_add_ptr (tb, 0, TAG_OCTET_STRING, data, datalen); tlv_builder_add_end (tb); tlv_builder_add_end (tb); err = tlv_builder_finalize (tb, &tmpder, &tmpderlen); if (err) goto leave; xfree (data); data = tmpder; datalen = tmpderlen; } err = scd_writecert (certref, data, datalen); leave: es_fclose (key); xfree (data); xfree (certref_buffer); return err; } static gpg_error_t cmd_readcert (card_info_t info, char *argstr) { gpg_error_t err; char *certref_buffer = NULL; char *certref; void *data = NULL; size_t datalen, dataoff; const char *fname; int opt_openpgp; if (!info) return print_help ("READCERT [--openpgp] CERTREF > FILE\n\n" "Read the certificate for key CERTREF and store it in FILE.\n" "With option \"--openpgp\" an OpenPGP keyblock is expected\n" "and stored in FILE.\n", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_openpgp = has_leading_option (argstr, "--openpgp"); argstr = skip_options (argstr); certref = argstr; if ((argstr = strchr (certref, ' '))) { *argstr++ = 0; trim_spaces (certref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = certref + strlen (certref); if (info->apptype == APP_TYPE_OPENPGP) { if (!ascii_strcasecmp (certref, "OPENPGP.3") || !strcmp (certref, "3")) certref_buffer = xstrdup ("OPENPGP.3"); else if (!ascii_strcasecmp (certref, "OPENPGP.2")||!strcmp (certref,"2")) certref_buffer = xstrdup ("OPENPGP.2"); else if (!ascii_strcasecmp (certref, "OPENPGP.1")||!strcmp (certref,"1")) certref_buffer = xstrdup ("OPENPGP.1"); else { err = gpg_error (GPG_ERR_INV_ID); log_error ("Error: CERTREF must be OPENPGP.N or just N" " with N being 1..3\""); goto leave; } certref = certref_buffer; } if (*argstr == '>') /* Write it to a file */ { for (argstr++; spacep (argstr); argstr++) ; fname = argstr; } else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } dataoff = 0; err = scd_readcert (certref, &data, &datalen); if (err) goto leave; if (opt_openpgp) { /* Check whether DATA contains an OpenPGP keyblock and put only * this into FILE. If the data is something different, return * an error. */ const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, cons, ndef; p = data; n = datalen; if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) goto not_openpgp; /* Does not start with a sequence. */ if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons)) goto not_openpgp; /* No Object ID. */ if (objlen > n) goto not_openpgp; /* Inconsistent lengths. */ if (objlen != 10 || memcmp (p, "\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", objlen)) goto not_openpgp; /* Wrong Object ID. */ p += objlen; n -= objlen; if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_CONTEXT && tag == 0 && cons)) goto not_openpgp; /* Not a [0] context tag. */ if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING && !cons)) goto not_openpgp; /* Not an octet string. */ if (objlen > n) goto not_openpgp; /* Inconsistent lengths. */ dataoff = p - (const unsigned char*)data; datalen = objlen; } err = put_data_to_file (fname, (unsigned char*)data+dataoff, datalen); goto leave; not_openpgp: err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE); leave: xfree (data); xfree (certref_buffer); return err; } static gpg_error_t cmd_writekey (card_info_t info, char *argstr) { gpg_error_t err; int opt_force; const char *argv[2]; int argc; char *keyref_buffer = NULL; const char *keyref; const char *keygrip; if (!info) return print_help ("WRITEKEY [--force] KEYREF KEYGRIP\n\n" "Write a private key object identified by KEYGRIP to slot KEYREF.\n" "Use --force to overwrite an existing key.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_force = has_leading_option (argstr, "--force"); argstr = skip_options (argstr); argc = split_fields (argstr, argv, DIM (argv)); if (argc < 2) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } /* Upcase the keyref; prepend cardtype if needed. */ keyref = argv[0]; if (!strchr (keyref, '.')) keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", keyref, NULL); else keyref_buffer = xstrdup (keyref); ascii_strupr (keyref_buffer); keyref = keyref_buffer; /* Get the keygrip. */ keygrip = argv[1]; if (strlen (keygrip) != 40 && !(keygrip[0] == '&' && strlen (keygrip+1) == 40)) { log_error (_("Not a valid keygrip (expecting 40 hex digits)\n")); err = gpg_error (GPG_ERR_INV_ARG); goto leave; } err = scd_writekey (keyref, opt_force, keygrip); leave: xfree (keyref_buffer); return err; } static gpg_error_t cmd_forcesig (card_info_t info) { gpg_error_t err; int newstate; if (!info) return print_help ("FORCESIG\n\n" "Toggle the forcesig flag of an OpenPGP card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } newstate = !info->chv1_cached; err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1); if (err) goto leave; /* Read it back to be sure we have the right toggle state the next * time. */ err = scd_getattr ("CHV-STATUS", info); leave: return err; } /* Helper for cmd_generate_openpgp. Note that either 0 or 1 is stored at * FORCED_CHV1. */ static gpg_error_t check_pin_for_key_operation (card_info_t info, int *forced_chv1) { gpg_error_t err = 0; *forced_chv1 = !info->chv1_cached; if (*forced_chv1) { /* Switch off the forced mode so that during key generation we * don't get bothered with PIN queries for each self-signature. */ err = scd_setattr ("CHV-STATUS-1", "\x01", 1); if (err) { log_error ("error clearing forced signature PIN flag: %s\n", gpg_strerror (err)); *forced_chv1 = -1; /* Not changed. */ goto leave; } } /* Check the PIN now, so that we won't get asked later for each * binding signature. */ err = scd_checkpin (info->serialno); if (err) log_error ("error checking the PIN: %s\n", gpg_strerror (err)); leave: return err; } /* Helper for cmd_generate_openpgp. */ static void restore_forced_chv1 (int *forced_chv1) { gpg_error_t err; /* Note the possible values stored at FORCED_CHV1: * 0 - forcesig was not enabled. * 1 - forcesig was enabled - enable it again. * -1 - We have not changed anything. */ if (*forced_chv1 == 1) { /* Switch back to forced state. */ err = scd_setattr ("CHV-STATUS-1", "", 1); if (err) log_error ("error setting forced signature PIN flag: %s\n", gpg_strerror (err)); *forced_chv1 = 0; } } /* Ask whether existing keys shall be overwritten. With NULL used for * KINFO it will ask for all keys, other wise for the given key. */ static gpg_error_t ask_replace_keys (key_info_t kinfo) { gpg_error_t err; char *answer; tty_printf ("\n"); if (kinfo) log_info (_("Note: key %s is already stored on the card!\n"), kinfo->keyref); else log_info (_("Note: Keys are already stored on the card!\n")); tty_printf ("\n"); if (kinfo) answer = tty_getf (_("Replace existing key %s ? (y/N) "), kinfo->keyref); else answer = tty_get (_("Replace existing keys? (y/N) ")); tty_kill_prompt (); if (*answer == CONTROL_D) err = gpg_error (GPG_ERR_CANCELED); else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) err = gpg_error (GPG_ERR_CANCELED); else err = 0; xfree (answer); return err; } /* Implementation of cmd_generate for OpenPGP cards to generate all * standard keys at once. */ static gpg_error_t generate_all_openpgp_card_keys (card_info_t info, char **algos) { gpg_error_t err; int forced_chv1 = -1; int want_backup; char *answer = NULL; key_info_t kinfo1, kinfo2, kinfo3; if (info->extcap.ki) { xfree (answer); answer = tty_get (_("Make off-card backup of encryption key? (Y/n) ")); want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/); tty_kill_prompt (); if (*answer == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } else want_backup = 0; kinfo1 = find_kinfo (info, "OPENPGP.1"); kinfo2 = find_kinfo (info, "OPENPGP.2"); kinfo3 = find_kinfo (info, "OPENPGP.3"); if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen)) || (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen)) || (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen)) ) { err = ask_replace_keys (NULL); if (err) goto leave; } /* If no displayed name has been set, we assume that this is a fresh * card and print a hint about the default PINs. */ if (!info->disp_name || !*info->disp_name) { tty_printf ("\n"); tty_printf (_("Please note that the factory settings of the PINs are\n" " PIN = '%s' Admin PIN = '%s'\n" "You should change them using the command --change-pin\n"), OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT); tty_printf ("\n"); } err = check_pin_for_key_operation (info, &forced_chv1); if (err) goto leave; (void)algos; /* FIXME: If we have ALGOS, we need to change the key attr. */ /* FIXME: We need to divert to a function which spawns gpg which * will then create the key. This also requires new features in * gpg. We might also first create the keys on the card and then * tell gpg to use them to create the OpenPGP keyblock. */ /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */ (void)want_backup; err = scd_genkey ("OPENPGP.1", 1, NULL, NULL); leave: restore_forced_chv1 (&forced_chv1); xfree (answer); return err; } /* Create a single key. This is a helper for cmd_generate. */ static gpg_error_t generate_key (card_info_t info, const char *keyref, int force, const char *algo) { gpg_error_t err; key_info_t kinfo; if (info->apptype == APP_TYPE_OPENPGP) { kinfo = find_kinfo (info, keyref); if (!kinfo) { err = gpg_error (GPG_ERR_INV_ID); goto leave; } if (!force && kinfo->fprlen && !mem_is_zero (kinfo->fpr, kinfo->fprlen)) { err = ask_replace_keys (NULL); if (err) goto leave; force = 1; } } err = scd_genkey (keyref, force, algo, NULL); leave: return err; } static gpg_error_t cmd_generate (card_info_t info, char *argstr) { static char * const valid_algos[] = { "rsa2048", "rsa3072", "rsa4096", "", "nistp256", "nistp384", "nistp521", "", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "", "ed25519", "cv25519", NULL }; gpg_error_t err; int opt_force; char *p; char **opt_algo = NULL; /* Malloced. */ char *keyref_buffer = NULL; /* Malloced. */ char *keyref; /* Points into argstr or keyref_buffer. */ int i, j; if (!info) return print_help ("GENERATE [--force] [--algo=ALGO{+ALGO2}] KEYREF\n\n" "Create a new key on a card.\n" "Use --force to overwrite an existing key.\n" "Use \"help\" for ALGO to get a list of known algorithms.\n" "For OpenPGP cards several algos may be given.\n" "Note that the OpenPGP key generation is done interactively\n" "unless a single ALGO or KEYREF are given.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); opt_force = has_leading_option (argstr, "--force"); err = get_option_value (argstr, "--algo", &p); if (err) goto leave; if (p) { opt_algo = strtokenize (p, "+"); if (!opt_algo) { err = gpg_error_from_syserror (); xfree (p); goto leave; } xfree (p); } argstr = skip_options (argstr); keyref = argstr; if ((argstr = strchr (keyref, ' '))) { *argstr++ = 0; trim_spaces (keyref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = keyref + strlen (keyref); if (!*keyref) keyref = NULL; if (*argstr) { /* Extra arguments found. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (opt_algo) { /* opt_algo is an array of algos. */ for (i=0; opt_algo[i]; i++) { for (j=0; valid_algos[j]; j++) if (*valid_algos[j] && !strcmp (valid_algos[j], opt_algo[i])) break; if (!valid_algos[j]) { int lf = 1; if (!ascii_strcasecmp (opt_algo[i], "help")) log_info ("Known algorithms:\n"); else { log_info ("Invalid algorithm '%s' given. Use one of:\n", opt_algo[i]); err = gpg_error (GPG_ERR_PUBKEY_ALGO); } for (i=0; valid_algos[i]; i++) { if (!*valid_algos[i]) lf = 1; else if (lf) { lf = 0; log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); } else log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); } log_printf ("\n"); show_keysize_warning (); goto leave; } } } /* Upcase the keyref; if it misses the cardtype, prepend it. */ if (keyref) { if (!strchr (keyref, '.')) keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", keyref, NULL); else keyref_buffer = xstrdup (keyref); ascii_strupr (keyref_buffer); keyref = keyref_buffer; } /* Special checks. */ if ((info->cardtype && !strcmp (info->cardtype, "yubikey")) && info->cardversion >= 0x040200 && info->cardversion < 0x040305) { log_error ("On-chip key generation on this YubiKey has been blocked.\n"); log_info ("Please see for details\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } /* Divert to dedicated functions. */ if (info->apptype == APP_TYPE_OPENPGP && !keyref && (!opt_algo || (opt_algo[0] && opt_algo[1]))) { /* With no algo requested or more than one algo requested and no * keyref given we create all keys. */ if (opt_force || keyref) log_info ("Note: OpenPGP key generation is interactive.\n"); err = generate_all_openpgp_card_keys (info, opt_algo); } else if (!keyref) err = gpg_error (GPG_ERR_INV_ID); else if (opt_algo && opt_algo[0] && opt_algo[1]) { log_error ("only one algorithm expected as value for --algo.\n"); err = gpg_error (GPG_ERR_INV_ARG); } else err = generate_key (info, keyref, opt_force, opt_algo? opt_algo[0]:NULL); if (!err) { err = scd_learn (info, 0); if (err) log_error ("Error re-reading card: %s\n", gpg_strerror (err)); } leave: xfree (opt_algo); xfree (keyref_buffer); return err; } /* Change a PIN. */ static gpg_error_t cmd_passwd (card_info_t info, char *argstr) { gpg_error_t err = 0; char *answer = NULL; const char *pinref = NULL; int reset_mode = 0; int nullpin = 0; int menu_used = 0; if (!info) return print_help ("PASSWD [--reset|--nullpin] [PINREF]\n\n" "Change or unblock the PINs. Note that in interactive mode\n" "and without a PINREF a menu is presented for certain cards;\n" "in non-interactive and without a PINREF a default value is\n" "used for these cards. The option --reset is used with TCOS\n" "cards to reset the PIN using the PUK or vice versa; --nullpin\n" "is used for these cards to set the initial PIN.", 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (has_option (argstr, "--reset")) reset_mode = 1; else if (has_option (argstr, "--nullpin")) nullpin = 1; argstr = skip_options (argstr); /* If --reset or --nullpin has been given we force non-interactive mode. */ if (*argstr || reset_mode || nullpin) { pinref = argstr; if (!*pinref) { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } } else if (opt.interactive && info->apptype == APP_TYPE_OPENPGP) { menu_used = 1; while (!pinref) { xfree (answer); answer = get_selection ("1 - change the PIN\n" "2 - unblock and set new a PIN\n" "3 - change the Admin PIN\n" "4 - set the Reset Code\n" "Q - quit\n"); if (strlen (answer) != 1) continue; else if (*answer == 'q' || *answer == 'Q') goto leave; else if (*answer == '1') pinref = "OPENPGP.1"; else if (*answer == '2') { pinref = "OPENPGP.1"; reset_mode = 1; } else if (*answer == '3') pinref = "OPENPGP.3"; else if (*answer == '4') { pinref = "OPENPGP.2"; reset_mode = 1; } } } else if (info->apptype == APP_TYPE_OPENPGP) pinref = "OPENPGP.1"; else if (opt.interactive && info->apptype == APP_TYPE_PIV) { menu_used = 1; while (!pinref) { xfree (answer); answer = get_selection ("1 - change the PIN\n" "2 - change the PUK\n" "3 - change the Global PIN\n" "Q - quit\n"); if (strlen (answer) != 1) ; else if (*answer == 'q' || *answer == 'Q') goto leave; else if (*answer == '1') pinref = "PIV.80"; else if (*answer == '2') pinref = "PIV.81"; else if (*answer == '3') pinref = "PIV.00"; } } else if (opt.interactive && info->apptype == APP_TYPE_NKS) { int for_qualified = 0; menu_used = 1; log_assert (DIM (info->chvinfo) >= 4); /* If there is a qualified signature use a menu to select * between standard PIN and QES PINs. */ if (info->chvinfo[2] != -2 || info->chvinfo[3] != -2) { for (;;) { xfree (answer); answer = get_selection (" 1 - Standard PIN/PUK\n" " 2 - PIN/PUK for qualified signature\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) break; else if (!strcmp (answer, "2")) { for_qualified = 1; break; } } } if (info->chvinfo[for_qualified? 2 : 0] == -4) { while (!pinref) { xfree (answer); answer = get_selection ("The NullPIN is still active on this card.\n" "You need to choose and set a PIN first.\n" "\n" " 1 - Set your PIN\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; nullpin = 1; } } } else { while (!pinref) { xfree (answer); answer = get_selection (" 1 - change PIN\n" " 2 - reset PIN\n" " 3 - change PUK\n" " 4 - reset PUK\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; } else if (!strcmp (answer, "2")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; reset_mode = 1; } else if (!strcmp (answer, "3")) { pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH"; } else if (!strcmp (answer, "4")) { pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH"; reset_mode = 1; } } } } else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } err = scd_change_pin (pinref, reset_mode, nullpin); if (err) { if (!opt.interactive && !menu_used && !opt.verbose) ; else if (gpg_err_code (err) == GPG_ERR_CANCELED && gpg_err_source (err) == GPG_ERR_SOURCE_PINENTRY) log_info ("%s\n", gpg_strerror (err)); else if (!ascii_strcasecmp (pinref, "PIV.81")) log_error ("Error changing the PUK.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode) log_error ("Error unblocking the PIN.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode) log_error ("Error setting the Reset Code.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.3")) log_error ("Error changing the Admin PIN.\n"); else if (reset_mode) log_error ("Error resetting the PIN.\n"); else log_error ("Error changing the PIN.\n"); } else { if (!opt.interactive && !opt.verbose) ; else if (!ascii_strcasecmp (pinref, "PIV.81")) log_info ("PUK changed.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode) log_info ("PIN unblocked and new PIN set.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode) log_info ("Reset Code set.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.3")) log_info ("Admin PIN changed.\n"); else if (reset_mode) log_info ("PIN reset.\n"); else log_info ("PIN changed.\n"); /* Update the CHV status. */ err = scd_getattr ("CHV-STATUS", info); } leave: xfree (answer); return err; } static gpg_error_t cmd_unblock (card_info_t info) { gpg_error_t err = 0; if (!info) return print_help ("UNBLOCK\n\n" "Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n" "cards prior to version 2 can't use this; instead the PASSWD\n" "command can be used to set a new PIN.", 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (info->apptype == APP_TYPE_OPENPGP) { if (!info->is_v2) { log_error (_("This command is only available for version 2 cards\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else if (!info->chvinfo[1]) { log_error (_("Reset Code not or not anymore available\n")); err = gpg_error (GPG_ERR_PIN_BLOCKED); } else { err = scd_change_pin ("OPENPGP.2", 0, 0); if (!err) log_info ("PIN changed.\n"); } } else if (info->apptype == APP_TYPE_PIV) { /* Unblock the Application PIN. */ err = scd_change_pin ("PIV.80", 1, 0); if (!err) log_info ("PIN unblocked and changed.\n"); } else { log_info ("Unblocking not supported for '%s'.\n", app_type_string (info->apptype)); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } return err; } /* Note: On successful execution a redisplay should be scheduled. If * this function fails the card may be in an unknown state. */ static gpg_error_t cmd_factoryreset (card_info_t info) { gpg_error_t err; char *answer = NULL; int termstate = 0; int any_apdu = 0; int is_yubikey = 0; int locked = 0; int i; if (!info) return print_help ("FACTORY-RESET\n\n" "Do a complete reset of some OpenPGP and PIV cards. This\n" "deletes all data and keys and resets the PINs to their default.\n" "This is mainly used by developers with scratch cards. Don't\n" "worry, you need to confirm before the command proceeds.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); /* We support the factory reset for most OpenPGP cards and Yubikeys * with the PIV application. */ if (info->apptype == APP_TYPE_OPENPGP) ; else if (info->apptype == APP_TYPE_PIV && info->cardtype && !strcmp (info->cardtype, "yubikey")) is_yubikey = 1; else return gpg_error (GPG_ERR_NOT_SUPPORTED); /* For an OpenPGP card the code below basically does the same what * this gpg-connect-agent script does: * * scd reset * scd serialno undefined * scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 e6 00 00 * scd apdu 00 44 00 00 * scd reset * /echo Card has been reset to factory defaults * * For a PIV application on a Yubikey it merely issues the Yubikey * specific resset command. */ err = scd_learn (info, 0); if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) termstate = 1; else if (err) { log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); goto leave; } if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (!termstate || is_yubikey) { if (!is_yubikey) { if (!(info->status_indicator == 3 || info->status_indicator == 5)) { /* Note: We won't see status-indicator 3 here because it * is not possible to select a card application in * termination state. */ log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } } tty_printf ("\n"); log_info (_("Note: This command destroys all keys stored on the card!\n")); tty_printf ("\n"); xfree (answer); answer = tty_get (_("Continue? (y/N) ")); tty_kill_prompt (); trim_spaces (answer); if (*answer == CONTROL_D || !answer_is_yes_no_default (answer, 0/*(default to no)*/)) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } xfree (answer); answer = tty_get (_("Really do a factory reset? (enter \"yes\") ")); tty_kill_prompt (); trim_spaces (answer); if (strcmp (answer, "yes") && strcmp (answer,_("yes"))) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (is_yubikey) { /* If the PIV application is already selected, we only need to * send the special reset APDU after having blocked PIN and * PUK. Note that blocking the PUK is done using the * unblock PIN command. */ any_apdu = 1; for (i=0; i < 5; i++) send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff, NULL, NULL); for (i=0; i < 5; i++) send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "RESET RETRY COUNTER", 0xffff, NULL, NULL); err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL); if (err) goto leave; } else /* OpenPGP card. */ { any_apdu = 1; /* We need to select a card application before we can send * APDUs to the card without scdaemon doing anything on its * own. We then lock the connection so that other tools * (e.g. Kleopatra) don't try a new select. */ err = send_apdu ("lock", "locking connection ", 0, NULL, NULL); if (err) goto leave; locked = 1; err = send_apdu ("reset-keep-lock", "reset", 0, NULL, NULL); if (err) goto leave; err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL); if (err) goto leave; /* Select the OpenPGP application. */ err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0, NULL, NULL); if (err) goto leave; /* Do some dummy verifies with wrong PINs to set the retry * counter to zero. We can't easily use the card version 2.1 * feature of presenting the admin PIN to allow the terminate * command because there is no machinery in scdaemon to catch * the verify command and ask for the PIN when the "APDU" * command is used. * Here, the length of dummy wrong PIN is 32-byte, also * supporting authentication with KDF DO. */ for (i=0; i < 4; i++) send_apdu ("0020008120" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff, NULL, NULL); for (i=0; i < 4; i++) send_apdu ("0020008320" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff, NULL, NULL); /* Send terminate datafile command. */ err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL); if (err) goto leave; } } if (!is_yubikey) { any_apdu = 1; /* Send activate datafile command. This is used without * confirmation if the card is already in termination state. */ err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL); if (err) goto leave; } /* Finally we reset the card reader once more. */ if (locked) err = send_apdu ("reset-keep-lock", "reset", 0, NULL, NULL); else err = send_apdu (NULL, "RESET", 0, NULL, NULL); if (err) goto leave; /* Then, connect the card again. */ err = scd_serialno (NULL, NULL); if (!err) info->need_sn_cmd = 0; leave: if (err && any_apdu && !is_yubikey) { log_info ("Due to an error the card might be in an inconsistent state\n" "You should run the LIST command to check this.\n"); /* FIXME: We need a better solution in the case that the card is * in a termination state, i.e. the card was removed before the * activate was sent. The best solution I found with v2.1 * Zeitcontrol card was to kill scdaemon and the issue this * sequence with gpg-connect-agent: * scd reset * scd serialno undefined * scd apdu 00A4040006D27600012401 (returns error) * scd apdu 00440000 * Then kill scdaemon again and issue: * scd reset * scd serialno openpgp */ } if (locked) send_apdu ("unlock", "unlocking connection ", 0, NULL, NULL); xfree (answer); return err; } /* Generate KDF data. This is a helper for cmd_kdfsetup. */ static gpg_error_t gen_kdf_data (unsigned char *data, int single_salt) { gpg_error_t err; const unsigned char h0[] = { 0x81, 0x01, 0x03, 0x82, 0x01, 0x08, 0x83, 0x04 }; const unsigned char h1[] = { 0x84, 0x08 }; const unsigned char h2[] = { 0x85, 0x08 }; const unsigned char h3[] = { 0x86, 0x08 }; const unsigned char h4[] = { 0x87, 0x20 }; const unsigned char h5[] = { 0x88, 0x20 }; unsigned char *p, *salt_user, *salt_admin; unsigned char s2k_char; unsigned int iterations; unsigned char count_4byte[4]; p = data; s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); iterations = S2K_DECODE_COUNT (s2k_char); count_4byte[0] = (iterations >> 24) & 0xff; count_4byte[1] = (iterations >> 16) & 0xff; count_4byte[2] = (iterations >> 8) & 0xff; count_4byte[3] = (iterations & 0xff); memcpy (p, h0, sizeof h0); p += sizeof h0; memcpy (p, count_4byte, sizeof count_4byte); p += sizeof count_4byte; memcpy (p, h1, sizeof h1); salt_user = (p += sizeof h1); gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; if (single_salt) salt_admin = salt_user; else { memcpy (p, h2, sizeof h2); p += sizeof h2; gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; memcpy (p, h3, sizeof h3); salt_admin = (p += sizeof h3); gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; } memcpy (p, h4, sizeof h4); p += sizeof h4; err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT, strlen (OPENPGP_USER_PIN_DEFAULT), GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, salt_user, 8, iterations, 32, p); p += 32; if (!err) { memcpy (p, h5, sizeof h5); p += sizeof h5; err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT, strlen (OPENPGP_ADMIN_PIN_DEFAULT), GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, salt_admin, 8, iterations, 32, p); } return err; } static gpg_error_t cmd_kdfsetup (card_info_t info, char *argstr) { gpg_error_t err; unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX]; int single = (*argstr != 0); if (!info) return print_help ("KDF-SETUP\n\n" "Prepare the OpenPGP card KDF feature for this card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!info->extcap.kdf) { log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } err = gen_kdf_data (kdf_data, single); if (err) goto leave; err = scd_setattr ("KDF", kdf_data, single ? OPENPGP_KDF_DATA_LENGTH_MIN /* */ : OPENPGP_KDF_DATA_LENGTH_MAX); if (err) goto leave; err = scd_getattr ("KDF", info); leave: return err; } static void show_keysize_warning (void) { static int shown; if (shown) return; shown = 1; tty_printf (_("Note: There is no guarantee that the card supports the requested\n" " key type or size. If the key generation does not succeed,\n" " please check the documentation of your card to see which\n" " key types and sizes are supported.\n") ); } static gpg_error_t cmd_uif (card_info_t info, char *argstr) { gpg_error_t err; int keyno; char name[50]; unsigned char data[2]; char *answer = NULL; int opt_yes; if (!info) return print_help ("UIF N [on|off|permanent]\n\n" "Change the User Interaction Flag. N must in the range 1 to 3.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); if (!info->extcap.bt) { log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } opt_yes = has_leading_option (argstr, "--yes"); argstr = skip_options (argstr); if (digitp (argstr)) { keyno = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else keyno = 0; if (keyno < 1 || keyno > 3) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if ( !strcmp (argstr, "off") ) data[0] = 0x00; else if ( !strcmp (argstr, "on") ) data[0] = 0x01; else if ( !strcmp (argstr, "permanent") ) data[0] = 0x02; else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } data[1] = 0x20; log_assert (keyno - 1 < DIM(info->uif)); if (info->uif[keyno-1] == 2) { log_info (_("User Interaction Flag is set to \"%s\" - can't change\n"), "permanent"); err = gpg_error (GPG_ERR_INV_STATE); goto leave; } if (data[0] == 0x02) { if (opt.interactive) { tty_printf (_("Warning: Setting the User Interaction Flag to \"%s\"\n" " can only be reverted using a factory reset!\n" ), "permanent"); answer = tty_get (_("Continue? (y/N) ")); tty_kill_prompt (); if (*answer == CONTROL_D) err = gpg_error (GPG_ERR_CANCELED); else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) err = gpg_error (GPG_ERR_CANCELED); else err = 0; } else if (!opt_yes) { log_info (_("Warning: Setting the User Interaction Flag to \"%s\"\n" " can only be reverted using a factory reset!\n" ), "permanent"); log_info (_("Please use \"uif --yes %d %s\"\n"), keyno, "permanent"); err = gpg_error (GPG_ERR_CANCELED); } else err = 0; if (err) goto leave; } snprintf (name, sizeof name, "UIF-%d", keyno); err = scd_setattr (name, data, 2); if (!err) /* Read all UIF attributes again. */ err = scd_getattr ("UIF", info); leave: xfree (answer); return err; } static gpg_error_t cmd_yubikey (card_info_t info, char *argstr) { gpg_error_t err, err2; estream_t fp = opt.interactive? NULL : es_stdout; const char *words[20]; int nwords; if (!info) return print_help ("YUBIKEY args\n\n" "Various commands pertaining to Yubikey tokens with being:\n" "\n" " LIST \n" "\n" "List supported and enabled applications.\n" "\n" " ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" " DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" "\n" "Enable or disable the specified or all applications on the\n" "given interface.", 0); argstr = skip_options (argstr); if (!info->cardtype || strcmp (info->cardtype, "yubikey")) { log_info ("This command can only be used with Yubikeys.\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } nwords = split_fields (argstr, words, DIM (words)); if (nwords < 1) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } /* Note that we always do a learn to get a chance to the card back * into a usable state. */ err = yubikey_commands (info, fp, nwords, words); err2 = scd_learn (info, 0); if (err2) log_error ("Error re-reading card: %s\n", gpg_strerror (err2)); leave: return err; } static gpg_error_t cmd_apdu (card_info_t info, char *argstr) { gpg_error_t err; estream_t fp = opt.interactive? NULL : es_stdout; int with_atr; int handle_more; const char *s; const char *exlenstr; int exlenstrlen; char *options = NULL; unsigned int sw; unsigned char *result = NULL; size_t i, j, resultlen; if (!info) return print_help ("APDU [--more] [--exlen[=N]] \n" "\n" "Send an APDU to the current card. This command bypasses the high\n" "level functions and sends the data directly to the card. HEXSTRING\n" "is expected to be a proper APDU.\n" "\n" "Using the option \"--more\" handles the card status word MORE_DATA\n" "(61xx) and concatenates all responses to one block.\n" "\n" "Using the option \"--exlen\" the returned APDU may use extended\n" "length up to N bytes. If N is not given a default value is used.\n", 0); if (has_option (argstr, "--dump-atr")) with_atr = 2; else with_atr = has_option (argstr, "--atr"); handle_more = has_option (argstr, "--more"); exlenstr = has_option_name (argstr, "--exlen"); exlenstrlen = 0; if (exlenstr) { for (s=exlenstr; *s && !spacep (s); s++) exlenstrlen++; } argstr = skip_options (argstr); if (with_atr || handle_more || exlenstr) options = xasprintf ("%s%s%s%.*s", with_atr == 2? " --dump-atr": with_atr? " --data-atr":"", handle_more?" --more":"", exlenstr?" --exlen=":"", exlenstrlen, exlenstr?exlenstr:""); err = scd_apdu (argstr, options, &sw, &result, &resultlen); if (err) goto leave; if (!with_atr) { if (opt.interactive || opt.verbose) { char *p = scd_apdu_strerror (sw); log_info ("Statusword: 0x%04x (%s)\n", sw, p? p: "?"); xfree (p); } else log_info ("Statusword: 0x%04x\n", sw); } for (i=0; i < resultlen; ) { size_t save_i = i; tty_fprintf (fp, "D[%04X] ", (unsigned int)i); for (j=0; j < 16 ; j++, i++) { if (j == 8) tty_fprintf (fp, " "); if (i < resultlen) tty_fprintf (fp, " %02X", result[i]); else tty_fprintf (fp, " "); } tty_fprintf (fp, " "); i = save_i; for (j=0; j < 16; j++, i++) { unsigned int c = result[i]; if ( i >= resultlen ) tty_fprintf (fp, " "); else if (isascii (c) && isprint (c) && !iscntrl (c)) tty_fprintf (fp, "%c", c); else tty_fprintf (fp, "."); } tty_fprintf (fp, "\n"); } leave: xfree (result); xfree (options); return err; } static gpg_error_t cmd_gpg (card_info_t info, char *argstr, int use_gpgsm) { gpg_error_t err; char **argarray; ccparray_t ccp; const char **argv = NULL; gnupg_process_t proc; int i; if (!info) return print_help ("GPG[SM] \n" "\n" "Run gpg/gpgsm directly from this shell.\n", 0); /* Fixme: We need to write and use a version of strtokenize which * takes care of shell-style quoting. */ argarray = strtokenize (argstr, " \t\n\v"); if (!argarray) { err = gpg_error_from_syserror (); goto leave; } ccparray_init (&ccp, 0); for (i=0; argarray[i]; i++) ccparray_put (&ccp, argarray[i]); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_process_spawn (use_gpgsm? opt.gpgsm_program:opt.gpg_program, argv, (GNUPG_PROCESS_STDOUT_KEEP | GNUPG_PROCESS_STDERR_KEEP), NULL, NULL, &proc); if (!err) { err = gnupg_process_wait (proc, 1); gnupg_process_release (proc); } leave: xfree (argv); xfree (argarray); return err; } static gpg_error_t cmd_history (card_info_t info, char *argstr) { int opt_list, opt_clear; opt_list = has_option (argstr, "--list"); opt_clear = has_option (argstr, "--clear"); if (!info || !(opt_list || opt_clear)) return print_help ("HISTORY --list\n" " List the command history\n" "HISTORY --clear\n" " Clear the command history", 0); if (opt_list) tty_printf ("Sorry, history listing not yet possible\n"); if (opt_clear) tty_read_history (NULL, 0); return 0; } /* Data used by the command parser. This needs to be outside of the * function scope to allow readline based command completion. */ enum cmdids { cmdNOP = 0, cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, cmdGPG, cmdGPGSM, cmdHISTORY, cmdINVCMD }; static struct { const char *name; enum cmdids id; const char *desc; } cmds[] = { { "quit" , cmdQUIT, N_("quit this menu")}, { "q" , cmdQUIT, NULL }, { "bye" , cmdQUIT, NULL }, { "help" , cmdHELP, N_("show this help")}, { "?" , cmdHELP, NULL }, { "list" , cmdLIST, N_("list all available data")}, { "l" , cmdLIST, NULL }, { "name" , cmdNAME, N_("change card holder's name")}, { "url" , cmdURL, N_("change URL to retrieve key")}, { "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")}, { "login" , cmdLOGIN, N_("change the login name")}, { "lang" , cmdLANG, N_("change the language preferences")}, { "salutation",cmdSALUT, N_("change card holder's salutation")}, { "salut" , cmdSALUT, NULL }, { "cafpr" , cmdCAFPR , N_("change a CA fingerprint")}, { "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")}, { "generate", cmdGENERATE, N_("generate new keys")}, { "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")}, { "authenticate",cmdAUTH, N_("authenticate to the card")}, { "auth" , cmdAUTH, NULL }, { "reset" , cmdRESET, N_("send a reset to the card daemon")}, { "factory-reset",cmdFACTRST, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")}, { "uif", cmdUIF, N_("change the User Interaction Flag")}, { "privatedo", cmdPRIVATEDO, N_("change a private data object")}, { "readcert", cmdREADCERT, N_("read a certificate from a data object")}, { "writecert", cmdWRITECERT, N_("store a certificate to a data object")}, { "writekey", cmdWRITEKEY, N_("store a private key to a data object")}, { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, { "gpg", cmdGPG, NULL}, { "gpgsm", cmdGPGSM, NULL}, { "apdu", cmdAPDU, NULL}, { "history", cmdHISTORY, N_("manage the command history")}, { NULL, cmdINVCMD, NULL } }; /* The command line command dispatcher. */ static gpg_error_t dispatch_command (card_info_t info, const char *orig_command) { gpg_error_t err = 0; enum cmdids cmd; /* The command. */ char *command; /* A malloced copy of ORIG_COMMAND. */ char *argstr; /* The argument as a string. */ int i; int ignore_error; if ((ignore_error = *orig_command == '-')) orig_command++; command = xstrdup (orig_command); argstr = NULL; if ((argstr = strchr (command, ' '))) { *argstr++ = 0; trim_spaces (command); trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (command, cmds[i].name )) break; cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */ /* Make sure we have valid strings for the args. They are allowed * to be modified and must thus point to a buffer. */ if (!argstr) argstr = command + strlen (command); /* For most commands we need to make sure that we have a card. */ if (!info) ; /* Help mode */ else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP || cmd == cmdINVCMD) && !info->initialized) { err = scd_learn (info, 0); if (err) { err = fixup_scd_errors (err); log_error ("Error reading card: %s\n", gpg_strerror (err)); goto leave; } } if (info) info->card_removed = 0; switch (cmd) { case cmdNOP: if (!info) print_help ("NOP\n\n" "Dummy command.", 0); break; case cmdQUIT: if (!info) print_help ("QUIT\n\n" "Stop processing.", 0); else { err = gpg_error (GPG_ERR_EOF); goto leave; } break; case cmdHELP: if (!info) print_help ("HELP [command]\n\n" "Show all commands. With an argument show help\n" "for that command.", 0); else if (*argstr) dispatch_command (NULL, argstr); else { es_printf ("List of commands (\"help \" for details):\n"); for (i=0; cmds[i].name; i++ ) if(cmds[i].desc) es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); es_printf ("Prefix a command with a dash to ignore its error.\n"); } break; case cmdRESET: if (!info) print_help ("RESET\n\n" "Send a RESET to the card daemon.", 0); else { flush_keyblock_cache (); err = scd_apdu (NULL, NULL, NULL, NULL, NULL); if (!err) info->need_sn_cmd = 1; } break; case cmdLIST: err = cmd_list (info, argstr); break; case cmdVERIFY: err = cmd_verify (info, argstr); break; case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; case cmdLOGIN: err = cmd_login (info, argstr); break; case cmdLANG: err = cmd_lang (info, argstr); break; case cmdSALUT: err = cmd_salut (info, argstr); break; case cmdCAFPR: err = cmd_cafpr (info, argstr); break; case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTRST: err = cmd_factoryreset (info); break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break; case cmdGPG: err = cmd_gpg (info, argstr, 0); break; case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break; case cmdHISTORY: err = 0; break; /* Only used in interactive mode. */ case cmdINVCMD: default: log_error (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ leave: /* Return GPG_ERR_EOF only if its origin was "quit". */ es_fflush (es_stdout); if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT) err = gpg_error (GPG_ERR_GENERAL); if (!err && info && info->card_removed) { info->card_removed = 0; info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); } if (err && gpg_err_code (err) != GPG_ERR_EOF) { err = fixup_scd_errors (err); if (ignore_error) { log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err)); err = 0; } else { log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) info->need_sn_cmd = 1; } } xfree (command); return err; } /* The interactive main loop. */ static void interactive_loop (void) { gpg_error_t err; char *answer = NULL; /* The input line. */ enum cmdids cmd = cmdNOP; /* The command. */ char *argstr; /* The argument as a string. */ int redisplay = 1; /* Whether to redisplay the main info. */ char *help_arg = NULL; /* Argument of the HELP command. */ struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; char *p; int i; char *historyname = NULL; /* In the interactive mode we do not want to print the program prefix. */ log_set_prefix (NULL, 0); if (!opt.no_history) { historyname = make_filename (gnupg_homedir (), HISTORYNAME, NULL); if (tty_read_history (historyname, 500)) log_info ("error reading '%s': %s\n", historyname, gpg_strerror (gpg_error_from_syserror ())); } for (;;) { if (help_arg) { /* Clear info to indicate helpmode */ info = NULL; } else if (!info) { /* Get out of help. */ info = &info_buffer; help_arg = NULL; redisplay = 0; } else if (redisplay) { err = cmd_list (info, ""); if (err) { err = fixup_scd_errors (err); log_error ("Error reading card: %s\n", gpg_strerror (err)); } else { tty_printf("\n"); redisplay = 0; } } if (!info) { /* Copy the pending help arg into our answer. Note that * help_arg points into answer. */ p = xstrdup (help_arg); help_arg = NULL; xfree (answer); answer = p; } else { do { xfree (answer); tty_enable_completion (command_completion); answer = tty_get (_("gpg/card> ")); tty_kill_prompt(); tty_disable_completion (); trim_spaces(answer); } while ( *answer == '#' ); } argstr = NULL; if (!*answer) cmd = cmdLIST; /* We default to the list command */ else if (*answer == CONTROL_D) cmd = cmdQUIT; else { if ((argstr = strchr (answer,' '))) { *argstr++ = 0; trim_spaces (answer); trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (answer, cmds[i].name )) break; cmd = cmds[i].id; } /* Make sure we have valid strings for the args. They are * allowed to be modified and must thus point to a buffer. */ if (!argstr) argstr = answer + strlen (answer); if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP || cmd == cmdHISTORY || cmd == cmdINVCMD)) { /* If redisplay is set we know that there was an error reading * the card. In this case we force a LIST command to retry. */ if (!info) ; /* In help mode. */ else if (redisplay) { cmd = cmdLIST; } else if (!info->serialno) { /* Without a serial number most commands won't work. * Catch it here. */ if (cmd == cmdRESET || cmd == cmdLIST) info->need_sn_cmd = 1; else { tty_printf ("\n"); tty_printf ("Serial number missing\n"); continue; } } } if (info) info->card_removed = 0; err = 0; switch (cmd) { case cmdNOP: if (!info) print_help ("NOP\n\n" "Dummy command.", 0); break; case cmdQUIT: if (!info) print_help ("QUIT\n\n" "Leave this tool.", 0); else { tty_printf ("\n"); goto leave; } break; case cmdHELP: if (!info) print_help ("HELP [command]\n\n" "Show all commands. With an argument show help\n" "for that command.", 0); else if (*argstr) help_arg = argstr; /* Trigger help for a command. */ else { tty_printf ("List of commands (\"help \" for details):\n"); for (i=0; cmds[i].name; i++ ) if(cmds[i].desc) tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); } break; case cmdRESET: if (!info) print_help ("RESET\n\n" "Send a RESET to the card daemon.", 0); else { flush_keyblock_cache (); err = scd_apdu (NULL, NULL, NULL, NULL, NULL); if (!err) info->need_sn_cmd = 1; } break; case cmdLIST: err = cmd_list (info, argstr); break; case cmdVERIFY: err = cmd_verify (info, argstr); if (!err) redisplay = 1; break; case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; case cmdLOGIN: err = cmd_login (info, argstr); break; case cmdLANG: err = cmd_lang (info, argstr); break; case cmdSALUT: err = cmd_salut (info, argstr); break; case cmdCAFPR: err = cmd_cafpr (info, argstr); break; case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTRST: err = cmd_factoryreset (info); if (!err) redisplay = 1; break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break; case cmdGPG: err = cmd_gpg (info, argstr, 0); break; case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break; case cmdHISTORY: err = cmd_history (info, argstr); break; case cmdINVCMD: default: tty_printf ("\n"); tty_printf (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ if (!err && info && info->card_removed) { info->card_removed = 0; info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); } if (gpg_err_code (err) == GPG_ERR_CANCELED) tty_fprintf (NULL, "\n"); else if (err) { const char *s = "?"; for (i=0; cmds[i].name; i++ ) if (cmd == cmds[i].id) { s = cmds[i].name; break; } err = fixup_scd_errors (err); log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) info->need_sn_cmd = 1; } } /* End of main menu loop. */ leave: if (historyname && tty_write_history (historyname)) log_info ("error writing '%s': %s\n", historyname, gpg_strerror (gpg_error_from_syserror ())); release_card_info (info); xfree (historyname); xfree (answer); } #ifdef HAVE_LIBREADLINE /* Helper function for readline's command completion. */ static char * command_generator (const char *text, int state) { static int list_index, len; const char *name; /* If this is a new word to complete, initialize now. This includes * saving the length of TEXT for efficiency, and initializing the index variable to 0. */ if (!state) { list_index = 0; len = strlen(text); } /* Return the next partial match */ while ((name = cmds[list_index].name)) { /* Only complete commands that have help text. */ if (cmds[list_index++].desc && !strncmp (name, text, len)) return strdup(name); } return NULL; } /* Second helper function for readline's command completion. */ static char ** command_completion (const char *text, int start, int end) { (void)end; /* If we are at the start of a line, we try and command-complete. * If not, just do nothing for now. The support for help completion * needs to be more smarter. */ if (!start) return rl_completion_matches (text, command_generator); else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5)) return rl_completion_matches (text, command_generator); rl_attempted_completion_over = 1; return NULL; } #endif /*HAVE_LIBREADLINE*/ diff --git a/tools/mime-parser.c b/tools/mime-parser.c index 0db1a9c23..31f766ca5 100644 --- a/tools/mime-parser.c +++ b/tools/mime-parser.c @@ -1,833 +1,826 @@ /* mime-parser.c - Parse MIME structures (high level rfc822 parser). * Copyright (C) 2016 g10 Code GmbH * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GnuPG. * * This file 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. * * 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 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 . */ #include #include #include #include #include "../common/util.h" #include "rfc822parse.h" #include "mime-parser.h" enum pgpmime_states { PGPMIME_NONE = 0, PGPMIME_WAIT_ENCVERSION, PGPMIME_IN_ENCVERSION, PGPMIME_WAIT_ENCDATA, PGPMIME_IN_ENCDATA, PGPMIME_GOT_ENCDATA, PGPMIME_WAIT_SIGNEDDATA, PGPMIME_IN_SIGNEDDATA, PGPMIME_WAIT_SIGNATURE, PGPMIME_IN_SIGNATURE, PGPMIME_GOT_SIGNATURE, PGPMIME_INVALID }; /* Definition of the mime parser object. */ struct mime_parser_context_s { void *cookie; /* Cookie passed to all callbacks. */ /* The callback to announce the transition from header to body. */ gpg_error_t (*t2body) (void *cookie, int level); /* The callback to announce a new part. */ gpg_error_t (*new_part) (void *cookie, const char *mediatype, const char *mediasubtype); /* The callback to return data of a part. */ gpg_error_t (*part_data) (void *cookie, const void *data, size_t datalen); /* The callback to collect encrypted data. */ gpg_error_t (*collect_encrypted) (void *cookie, const char *data); /* The callback to collect signed data. */ gpg_error_t (*collect_signeddata) (void *cookie, const char *data); /* The callback to collect a signature. */ gpg_error_t (*collect_signature) (void *cookie, const char *data); /* The RFC822 parser context is stored here during callbacks. */ rfc822parse_t msg; /* Helper to convey error codes from user callbacks. */ gpg_error_t err; int nesting_level; /* The current nesting level. */ int hashing_at_level; /* The nesting level at which we are hashing. */ enum pgpmime_states pgpmime; /* Current PGP/MIME state. */ unsigned int delay_hashing:1;/* Helper for PGPMIME_IN_SIGNEDDATA. */ unsigned int want_part:1; /* Return the current part. */ unsigned int decode_part:2; /* Decode the part. 1 = QP, 2 = Base64. */ unsigned int verbose:1; /* Enable verbose mode. */ unsigned int debug:1; /* Enable debug mode. */ /* Flags to help with debug output. */ struct { unsigned int n_skip; /* Skip showing these number of lines. */ unsigned int header:1; /* Show the header lines. */ unsigned int data:1; /* Show the data lines. */ unsigned int as_note:1; /* Show the next data line as a note. */ unsigned int boundary : 1; } show; - struct b64state *b64state; /* NULL or malloced Base64 decoder state. */ + gpgrt_b64state_t b64state; /* NULL or malloced Base64 decoder state. */ /* A buffer for reading a mail line, */ char line[5000]; }; /* Print the event received by the parser for debugging. */ static void show_message_parser_event (rfc822parse_event_t event) { const char *s; switch (event) { case RFC822PARSE_OPEN: s= "Open"; break; case RFC822PARSE_CLOSE: s= "Close"; break; case RFC822PARSE_CANCEL: s= "Cancel"; break; case RFC822PARSE_T2BODY: s= "T2Body"; break; case RFC822PARSE_FINISH: s= "Finish"; break; case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break; case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break; case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break; case RFC822PARSE_BOUNDARY: s= "Boundary"; break; case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break; case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break; case RFC822PARSE_PREAMBLE: s= "Preamble"; break; case RFC822PARSE_EPILOGUE: s= "Epilogue"; break; default: s= "[unknown event]"; break; } log_debug ("*** RFC822 event %s\n", s); } /* Do in-place decoding of quoted-printable data of LENGTH in BUFFER. Returns the new length of the buffer and stores true at R_SLBRK if the line ended with a soft line break; false is stored if not. This function assumes that a complete line is passed in buffer. */ static size_t qp_decode (char *buffer, size_t length, int *r_slbrk) { char *d, *s; if (r_slbrk) *r_slbrk = 0; /* Fixme: We should remove trailing white space first. */ for (s=d=buffer; length; length--) { if (*s == '=') { if (length > 2 && hexdigitp (s+1) && hexdigitp (s+2)) { s++; *(unsigned char*)d++ = xtoi_2 (s); s += 2; length -= 2; } else if (length > 2 && s[1] == '\r' && s[2] == '\n') { /* Soft line break. */ s += 3; length -= 2; if (r_slbrk && length == 1) *r_slbrk = 1; } else if (length > 1 && s[1] == '\n') { /* Soft line break with only a Unix line terminator. */ s += 2; length -= 1; if (r_slbrk && length == 1) *r_slbrk = 1; } else if (length == 1) { /* Soft line break at the end of the line. */ s += 1; if (r_slbrk) *r_slbrk = 1; } else *d++ = *s++; } else *d++ = *s++; } return d - buffer; } /* This function is called by parse_mail to communicate events. This * callback communicates with the caller using a structure passed in * OPAQUE. Should return 0 on success or set ERRNO and return -1. */ static int parse_message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg) { mime_parser_t ctx = opaque; const char *s; int rc = 0; /* Make the RFC822 parser context available for callbacks. */ ctx->msg = msg; if (ctx->debug) show_message_parser_event (event); if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY) { /* We need to check here whether to start collecting signed data * because attachments might come without header lines and thus * we won't see the BEGIN_HEADER event. */ if (ctx->pgpmime == PGPMIME_WAIT_SIGNEDDATA) { if (ctx->debug) log_debug ("begin_hash\n"); ctx->hashing_at_level = ctx->nesting_level; ctx->pgpmime = PGPMIME_IN_SIGNEDDATA; ctx->delay_hashing = 0; } } if (event == RFC822PARSE_OPEN) { /* Initialize for a new message. */ ctx->show.header = 1; } else if (event == RFC822PARSE_T2BODY) { rfc822parse_field_t field; ctx->want_part = 0; ctx->decode_part = 0; if (ctx->t2body) { rc = ctx->t2body (ctx->cookie, ctx->nesting_level); if (rc) goto t2body_leave; } field = rfc822parse_parse_field (msg, "Content-Type", -1); if (field) { const char *s1, *s2; s1 = rfc822parse_query_media_type (field, &s2); if (s1) { if (ctx->verbose) log_debug ("h media: %*s%s %s\n", ctx->nesting_level*2, "", s1, s2); if (ctx->pgpmime == PGPMIME_WAIT_ENCVERSION) { if (!strcmp (s1, "application") && !strcmp (s2, "pgp-encrypted")) { if (ctx->debug) log_debug ("c begin_encversion\n"); ctx->pgpmime = PGPMIME_IN_ENCVERSION; } else { log_error ("invalid PGP/MIME structure;" " expected '%s', got '%s/%s'\n", "application/pgp-encrypted", s1, s2); ctx->pgpmime = PGPMIME_INVALID; } } else if (ctx->pgpmime == PGPMIME_WAIT_ENCDATA) { if (!strcmp (s1, "application") && !strcmp (s2, "octet-stream")) { if (ctx->debug) log_debug ("c begin_encdata\n"); ctx->pgpmime = PGPMIME_IN_ENCDATA; } else { log_error ("invalid PGP/MIME structure;" " expected '%s', got '%s/%s'\n", "application/octet-stream", s1, s2); ctx->pgpmime = PGPMIME_INVALID; } } else if (ctx->pgpmime == PGPMIME_WAIT_SIGNATURE) { if (!strcmp (s1, "application") && !strcmp (s2, "pgp-signature")) { if (ctx->debug) log_debug ("c begin_signature\n"); ctx->pgpmime = PGPMIME_IN_SIGNATURE; } else { log_error ("invalid PGP/MIME structure;" " expected '%s', got '%s/%s'\n", "application/pgp-signature", s1, s2); ctx->pgpmime = PGPMIME_INVALID; } } else if (!strcmp (s1, "multipart") && !strcmp (s2, "encrypted")) { s = rfc822parse_query_parameter (field, "protocol", 0); if (s) { if (ctx->debug) log_debug ("h encrypted.protocol: %s\n", s); if (!strcmp (s, "application/pgp-encrypted")) { if (ctx->pgpmime) log_error ("note: " "ignoring nested PGP/MIME signature\n"); else ctx->pgpmime = PGPMIME_WAIT_ENCVERSION; } else if (ctx->verbose) log_debug ("# this protocol is not supported\n"); } } else if (!strcmp (s1, "multipart") && !strcmp (s2, "signed")) { s = rfc822parse_query_parameter (field, "protocol", 1); if (s) { if (ctx->debug) log_debug ("h signed.protocol: %s\n", s); if (!strcmp (s, "application/pgp-signature")) { if (ctx->pgpmime) log_error ("note: " "ignoring nested PGP/MIME signature\n"); else ctx->pgpmime = PGPMIME_WAIT_SIGNEDDATA; } else if (ctx->verbose) log_debug ("# this protocol is not supported\n"); } } else if (ctx->new_part) { ctx->err = ctx->new_part (ctx->cookie, s1, s2); if (!ctx->err) ctx->want_part = 1; else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE) ctx->err = 0; else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE) { ctx->want_part = ctx->decode_part = 1; ctx->err = 0; } } } else { if (ctx->debug) log_debug ("h media: %*s none\n", ctx->nesting_level*2, ""); if (ctx->new_part) { ctx->err = ctx->new_part (ctx->cookie, "", ""); if (!ctx->err) ctx->want_part = 1; else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE) ctx->err = 0; else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE) { ctx->want_part = ctx->decode_part = 1; ctx->err = 0; } } } rfc822parse_release_field (field); } else { if (ctx->verbose) log_debug ("h media: %*stext plain [assumed]\n", ctx->nesting_level*2, ""); if (ctx->new_part) { ctx->err = ctx->new_part (ctx->cookie, "text", "plain"); if (!ctx->err) ctx->want_part = 1; else if (gpg_err_code (ctx->err) == GPG_ERR_FALSE) ctx->err = 0; else if (gpg_err_code (ctx->err) == GPG_ERR_TRUE) { ctx->want_part = ctx->decode_part = 1; ctx->err = 0; } } } /* Figure out the encoding if needed. */ if (ctx->decode_part) { char *value; size_t valueoff; ctx->decode_part = 0; /* Fallback for unknown encoding. */ value = rfc822parse_get_field (msg, "Content-Transfer-Encoding", -1, &valueoff); if (value) { if (!stricmp (value+valueoff, "quoted-printable")) ctx->decode_part = 1; else if (!stricmp (value+valueoff, "base64")) { ctx->decode_part = 2; if (ctx->b64state) - b64dec_finish (ctx->b64state); /* Reuse state. */ - else - { - ctx->b64state = xtrymalloc (sizeof *ctx->b64state); - if (!ctx->b64state) - rc = gpg_error_from_syserror (); - } - if (!rc) - rc = b64dec_start (ctx->b64state, NULL); + gpgrt_b64dec_finish (ctx->b64state); /* Release. */ + + ctx->b64state = gpgrt_b64dec_start (NULL); + if (!ctx->b64state) + rc = gpg_error_from_syserror (); } free (value); /* Right, we need a plain free. */ } } t2body_leave: ctx->show.header = 0; ctx->show.data = 1; ctx->show.n_skip = 1; } else if (event == RFC822PARSE_PREAMBLE) ctx->show.as_note = 1; else if (event == RFC822PARSE_LEVEL_DOWN) { if (ctx->debug) log_debug ("b down\n"); ctx->nesting_level++; } else if (event == RFC822PARSE_LEVEL_UP) { if (ctx->debug) log_debug ("b up\n"); if (ctx->nesting_level) ctx->nesting_level--; else log_error ("invalid structure (bad nesting level)\n"); } else if (event == RFC822PARSE_BOUNDARY || event == RFC822PARSE_LAST_BOUNDARY) { ctx->show.data = 0; ctx->show.boundary = 1; if (event == RFC822PARSE_BOUNDARY) { ctx->show.header = 1; ctx->show.n_skip = 1; if (ctx->debug) log_debug ("b part\n"); } else if (ctx->debug) log_debug ("b last\n"); if (ctx->pgpmime == PGPMIME_IN_ENCDATA) { if (ctx->debug) log_debug ("c end_encdata\n"); ctx->pgpmime = PGPMIME_GOT_ENCDATA; /* FIXME: We should assert (event == LAST_BOUNDARY). */ } else if (ctx->pgpmime == PGPMIME_IN_SIGNEDDATA && ctx->nesting_level == ctx->hashing_at_level) { if (ctx->debug) log_debug ("c end_hash\n"); ctx->pgpmime = PGPMIME_WAIT_SIGNATURE; if (ctx->collect_signeddata) ctx->err = ctx->collect_signeddata (ctx->cookie, NULL); } else if (ctx->pgpmime == PGPMIME_IN_SIGNATURE) { if (ctx->debug) log_debug ("c end_signature\n"); ctx->pgpmime = PGPMIME_GOT_SIGNATURE; /* FIXME: We should assert (event == LAST_BOUNDARY). */ } else if (ctx->want_part) { if (ctx->part_data) { /* FIXME: We may need to flush things. */ ctx->err = ctx->part_data (ctx->cookie, NULL, 0); } ctx->want_part = 0; } } ctx->msg = NULL; return rc; } /* Create a new mime parser object. COOKIE is a values which will be * used as first argument for all callbacks registered with this * parser object. */ gpg_error_t mime_parser_new (mime_parser_t *r_parser, void *cookie) { mime_parser_t ctx; *r_parser = NULL; ctx = xtrycalloc (1, sizeof *ctx); if (!ctx) return gpg_error_from_syserror (); ctx->cookie = cookie; *r_parser = ctx; return 0; } /* Release a mime parser object. */ void mime_parser_release (mime_parser_t ctx) { if (!ctx) return; if (ctx->b64state) - { - b64dec_finish (ctx->b64state); - xfree (ctx->b64state); - } + gpgrt_b64dec_finish (ctx->b64state); xfree (ctx); } /* Set verbose and debug mode. */ void mime_parser_set_verbose (mime_parser_t ctx, int level) { if (!level) { ctx->verbose = 0; ctx->debug = 0; } else { ctx->verbose = 1; if (level > 10) ctx->debug = 1; } } /* Set a callback for the transition from header to body. LEVEL is * the current nesting level, starting with 0. This callback can be * used to evaluate headers before any other action is done. Note * that if a new NEW_PART callback needs to be called it is done after * this T2BODY callback. */ void mime_parser_set_t2body (mime_parser_t ctx, gpg_error_t (*fnc) (void *cookie, int level)) { ctx->t2body = fnc; } /* Set the callback used to announce a new part. It will be called * with the media type and media subtype of the part. If no * Content-type header was given both values are the empty string. * The callback should return 0 on success or an error code. The * error code GPG_ERR_FALSE indicates that the caller is not * interested in the part and data shall not be returned via a * registered part_data callback. The error code GPG_ERR_TRUE * indicates that the parts shall be redurned in decoded format * (i.e. base64 or QP encoding is removed). */ void mime_parser_set_new_part (mime_parser_t ctx, gpg_error_t (*fnc) (void *cookie, const char *mediatype, const char *mediasubtype)) { ctx->new_part = fnc; } /* Set the callback used to return the data of a part to the caller. * The end of the part is indicated by passing NUL for DATA. */ void mime_parser_set_part_data (mime_parser_t ctx, gpg_error_t (*fnc) (void *cookie, const void *data, size_t datalen)) { ctx->part_data = fnc; } /* Set the callback to collect encrypted data. A NULL passed to the * callback indicates the end of the encrypted data; the callback may * then decrypt the collected data. */ void mime_parser_set_collect_encrypted (mime_parser_t ctx, gpg_error_t (*fnc) (void *cookie, const char *data)) { ctx->collect_encrypted = fnc; } /* Set the callback to collect signed data. A NULL passed to the * callback indicates the end of the signed data. */ void mime_parser_set_collect_signeddata (mime_parser_t ctx, gpg_error_t (*fnc) (void *cookie, const char *data)) { ctx->collect_signeddata = fnc; } /* Set the callback to collect the signature. A NULL passed to the * callback indicates the end of the signature; the callback may the * verify the signature. */ void mime_parser_set_collect_signature (mime_parser_t ctx, gpg_error_t (*fnc) (void *cookie, const char *data)) { ctx->collect_signature = fnc; } /* Return the RFC888 parser context. This is only available inside a * callback. */ rfc822parse_t mime_parser_rfc822parser (mime_parser_t ctx) { return ctx->msg; } /* Helper for mime_parser_parse. */ static gpg_error_t process_part_data (mime_parser_t ctx, char *line, size_t *length) { gpg_error_t err; size_t nbytes; if (!ctx->want_part) return 0; if (!ctx->part_data) return 0; if (ctx->decode_part == 1) { *length = qp_decode (line, *length, NULL); } else if (ctx->decode_part == 2) { log_assert (ctx->b64state); - err = b64dec_proc (ctx->b64state, line, *length, &nbytes); + err = gpgrt_b64dec_proc (ctx->b64state, line, *length, &nbytes); if (err) return err; *length = nbytes; } return ctx->part_data (ctx->cookie, line, *length); } /* Read and parse a message from FP and call the appropriate * callbacks. */ gpg_error_t mime_parser_parse (mime_parser_t ctx, estream_t fp) { gpg_error_t err; rfc822parse_t msg = NULL; unsigned int lineno = 0; size_t length; char *line; line = ctx->line; msg = rfc822parse_open (parse_message_cb, ctx); if (!msg) { err = gpg_error_from_syserror (); log_error ("can't open mail parser: %s", gpg_strerror (err)); goto leave; } /* Fixme: We should not use fgets because it can't cope with embedded nul characters. */ while (es_fgets (ctx->line, sizeof (ctx->line), fp)) { lineno++; if (lineno == 1 && !strncmp (line, "From ", 5)) continue; /* We better ignore a leading From line. */ length = strlen (line); if (length && line[length - 1] == '\n') line[--length] = 0; else log_error ("mail parser detected too long or" " non terminated last line (lnr=%u)\n", lineno); if (length && line[length - 1] == '\r') line[--length] = 0; ctx->err = 0; if (rfc822parse_insert (msg, line, length)) { err = gpg_error_from_syserror (); log_error ("mail parser failed: %s", gpg_strerror (err)); goto leave; } if (ctx->err) { /* Error from a callback detected. */ err = ctx->err; goto leave; } /* Debug output. Note that the boundary is shown before n_skip * is evaluated. */ if (ctx->show.boundary) { if (ctx->debug) log_debug ("# Boundary: %s\n", line); ctx->show.boundary = 0; } if (ctx->show.n_skip) ctx->show.n_skip--; else if (ctx->show.data) { if (ctx->show.as_note) { if (ctx->verbose) log_debug ("# Note: %s\n", line); ctx->show.as_note = 0; } else if (ctx->debug) log_debug ("# Data: %s\n", line); } else if (ctx->show.header && ctx->verbose) log_debug ("# Header: %s\n", line); if (ctx->pgpmime == PGPMIME_IN_ENCVERSION) { trim_trailing_spaces (line); if (!*line) ; /* Skip empty lines. */ else if (!strcmp (line, "Version: 1")) ctx->pgpmime = PGPMIME_WAIT_ENCDATA; else { log_error ("invalid PGP/MIME structure;" " garbage in pgp-encrypted part ('%s')\n", line); ctx->pgpmime = PGPMIME_INVALID; } } else if (ctx->pgpmime == PGPMIME_IN_ENCDATA) { if (ctx->collect_encrypted) { err = ctx->collect_encrypted (ctx->cookie, line); if (!err) err = ctx->collect_encrypted (ctx->cookie, "\r\n"); if (err) goto leave; } } else if (ctx->pgpmime == PGPMIME_GOT_ENCDATA) { ctx->pgpmime = PGPMIME_NONE; if (ctx->collect_encrypted) ctx->collect_encrypted (ctx->cookie, NULL); } else if (ctx->pgpmime == PGPMIME_IN_SIGNEDDATA) { /* If we are processing signed data, store the signed data. * We need to delay the hashing of the CR/LF because the * last line ending belongs to the next boundary. This is * the reason why we can't use the PGPMIME state as a * condition. */ if (ctx->debug) log_debug ("# hashing %s'%s'\n", ctx->delay_hashing? "CR,LF+":"", line); if (ctx->collect_signeddata) { if (ctx->delay_hashing) ctx->collect_signeddata (ctx->cookie, "\r\n"); ctx->collect_signeddata (ctx->cookie, line); } ctx->delay_hashing = 1; err = process_part_data (ctx, line, &length); if (err) goto leave; } else if (ctx->pgpmime == PGPMIME_IN_SIGNATURE) { if (ctx->collect_signeddata) { ctx->collect_signature (ctx->cookie, line); ctx->collect_signature (ctx->cookie, "\r\n"); } } else if (ctx->pgpmime == PGPMIME_GOT_SIGNATURE) { ctx->pgpmime = PGPMIME_NONE; if (ctx->collect_signeddata) ctx->collect_signature (ctx->cookie, NULL); } else { err = process_part_data (ctx, line, &length); if (err) goto leave; } } rfc822parse_close (msg); msg = NULL; err = 0; leave: rfc822parse_cancel (msg); return err; } diff --git a/tools/wks-util.c b/tools/wks-util.c index ee1305b00..49dbb6f8a 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -1,1480 +1,1483 @@ /* wks-utils.c - Common helper functions for wks tools * Copyright (C) 2016 g10 Code GmbH * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * This file is part of GnuPG. * * This file 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. * * 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 Lesser General Public License for more details. */ #include #include #include #include #include #include #include #include "../common/util.h" #include "../common/status.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/zb32.h" #include "../common/userids.h" #include "../common/mbox-util.h" #include "../common/sysutils.h" #include "mime-maker.h" #include "send-mail.h" #include "gpg-wks.h" /* The stream to output the status information. Output is disabled if this is NULL. */ static estream_t statusfp; /* Set the status FD. */ void wks_set_status_fd (int fd) { static int last_fd = -1; if (fd != -1 && last_fd == fd) return; if (statusfp && statusfp != es_stdout && statusfp != es_stderr) es_fclose (statusfp); statusfp = NULL; if (fd == -1) return; if (fd == 1) statusfp = es_stdout; else if (fd == 2) statusfp = es_stderr; else statusfp = es_fdopen (fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", fd, gpg_strerror (gpg_error_from_syserror ())); } last_fd = fd; } /* Write a status line with code NO followed by the output of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void wks_write_status (int no, const char *format, ...) { va_list arg_ptr; if (!statusfp) return; /* Not enabled. */ es_fputs ("[GNUPG:] ", statusfp); es_fputs (get_status_string (no), statusfp); if (format) { es_putc (' ', statusfp); va_start (arg_ptr, format); es_vfprintf (statusfp, format, arg_ptr); va_end (arg_ptr); } es_putc ('\n', statusfp); } /* Append UID to LIST and return the new item. On success LIST is * updated. C-style escaping is removed from UID. On error ERRNO is * set and NULL returned. */ static uidinfo_list_t append_to_uidinfo_list (uidinfo_list_t *list, const char *uid, time_t created, int expired, int revoked) { uidinfo_list_t r, sl; char *plainuid; plainuid = decode_c_string (uid); if (!plainuid) return NULL; sl = xtrymalloc (sizeof *sl + strlen (plainuid)); if (!sl) { xfree (plainuid); return NULL; } strcpy (sl->uid, plainuid); sl->created = created; sl->flags = 0; sl->mbox = mailbox_from_userid (plainuid, 0); sl->expired = !!expired; sl->revoked = !!revoked; sl->next = NULL; if (!*list) *list = sl; else { for (r = *list; r->next; r = r->next ) ; r->next = sl; } xfree (plainuid); return sl; } /* Free the list of uid infos at LIST. */ void free_uidinfo_list (uidinfo_list_t list) { while (list) { uidinfo_list_t tmp = list->next; xfree (list->mbox); xfree (list); list = tmp; } } static void debug_gpg_invocation (const char *func, const char **argv) { int i; if (!(opt.debug & DBG_EXTPROG_VALUE)) return; log_debug ("%s: exec '%s' with", func, opt.gpg_program); for (i=0; argv[i]; i++) log_printf (" '%s'", argv[i]); log_printf ("\n"); } struct get_key_status_parm_s { const char *fpr; int found; int count; }; static void get_key_status_cb (void *opaque, const char *keyword, char *args) { struct get_key_status_parm_s *parm = opaque; if (DBG_CRYPTO) log_debug ("%s: %s\n", keyword, args); if (!strcmp (keyword, "EXPORTED")) { parm->count++; if (!ascii_strcasecmp (args, parm->fpr)) parm->found = 1; } } /* Get a key by fingerprint from gpg's keyring and make sure that the * mail address ADDRSPEC is included in the key. If EXACT is set the * returned user id must match Addrspec exactly and not just in the * addr-spec (mailbox) part. The key is returned as a new memory * stream at R_KEY. If BINARY is set the returned key is * non-armored. */ gpg_error_t wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec, int exact, int binary) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; estream_t key = NULL; struct get_key_status_parm_s parm; char *filterexp = NULL; memset (&parm, 0, sizeof parm); *r_key = NULL; key = es_fopenmem (0, "w+b"); if (!key) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } /* Prefix the key with the MIME content type. */ if (!binary) es_fputs ("Content-Type: application/pgp-keys\n" "\n", key); filterexp = es_bsprintf ("keep-uid=%s= %s", exact? "uid":"mbox", addrspec); if (!filterexp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (opt.verbose < 2) ccparray_put (&ccp, "--quiet"); else ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); if (!binary) ccparray_put (&ccp, "--armor"); ccparray_put (&ccp, "--export-options=export-clean"); ccparray_put (&ccp, "--export-filter"); ccparray_put (&ccp, filterexp); ccparray_put (&ccp, "--export"); ccparray_put (&ccp, "--"); ccparray_put (&ccp, fingerprint); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } parm.fpr = fingerprint; debug_gpg_invocation (__func__, argv); err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, NULL, key, get_key_status_cb, &parm); if (!err && parm.count > 1) err = gpg_error (GPG_ERR_TOO_MANY); else if (!err && !parm.found) err = gpg_error (GPG_ERR_NOT_FOUND); if (err) { log_error ("export failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (key); *r_key = key; key = NULL; leave: es_fclose (key); xfree (argv); xfree (filterexp); return err; } /* Helper for wks_list_key and wks_filter_uid. */ static void key_status_cb (void *opaque, const char *keyword, char *args) { (void)opaque; if (DBG_CRYPTO) log_debug ("gpg status: %s %s\n", keyword, args); } /* Parse field 1 and set revoked and expired on return. */ static void set_expired_revoked (const char *string, int *expired, int *revoked) { *expired = *revoked = 0; /* Look at letters and stop at the first digit. */ for ( ;*string && !digitp (string); string++) { if (*string == 'e') *expired = 1; else if (*string == 'r') *revoked = 1; } } /* Run gpg on KEY and store the primary fingerprint at R_FPR and the * list of mailboxes at R_MBOXES. Returns 0 on success; on error NULL * is stored at R_FPR and R_MBOXES and an error code is returned. * R_FPR may be NULL if the fingerprint is not needed. */ gpg_error_t wks_list_key (estream_t key, char **r_fpr, uidinfo_list_t *r_mboxes) { gpg_error_t err; ccparray_t ccp; const char **argv; estream_t listing; char *line = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; char **fields = NULL; int nfields; int lnr; char *fpr = NULL; uidinfo_list_t mboxes = NULL; int expired, revoked; if (r_fpr) *r_fpr = NULL; *r_mboxes = NULL; /* Open a memory stream. */ listing = es_fopenmem (0, "w+b"); if (!listing) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (opt.verbose < 2) ccparray_put (&ccp, "--quiet"); else ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); ccparray_put (&ccp, "--with-colons"); ccparray_put (&ccp, "--dry-run"); ccparray_put (&ccp, "--import-options=import-minimal,import-show"); ccparray_put (&ccp, "--import"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } debug_gpg_invocation (__func__, argv); err = gnupg_exec_tool_stream (opt.gpg_program, argv, key, NULL, listing, key_status_cb, NULL); if (err) { log_error ("import failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (listing); lnr = 0; expired = revoked = 0; maxlen = 2048; /* Set limit. */ while ((len = es_read_line (listing, &line, &length_of_line, &maxlen)) > 0) { lnr++; if (!maxlen) { log_error ("received line too long\n"); err = gpg_error (GPG_ERR_LINE_TOO_LONG); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; /* log_debug ("line '%s'\n", line); */ xfree (fields); fields = strtokenize_nt (line, ":"); if (!fields) { err = gpg_error_from_syserror (); log_error ("strtokenize failed: %s\n", gpg_strerror (err)); goto leave; } for (nfields = 0; fields[nfields]; nfields++) ; if (!nfields) { err = gpg_error (GPG_ERR_INV_ENGINE); goto leave; } if (!strcmp (fields[0], "sec")) { /* gpg may return "sec" as the first record - but we do not * accept secret keys. */ err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (lnr == 1 && strcmp (fields[0], "pub")) { /* First record is not a public key. */ err = gpg_error (GPG_ERR_INV_ENGINE); goto leave; } if (!strcmp (fields[0], "pub")) { if (lnr > 1) { /* More than one public key. */ err = gpg_error (GPG_ERR_TOO_MANY); goto leave; } if (nfields > 1) set_expired_revoked (fields[1], &expired, &revoked); else expired = revoked = 0; } if (!strcmp (fields[0], "sub") || !strcmp (fields[0], "ssb")) break; /* We can stop parsing here. */ if (!strcmp (fields[0], "fpr") && nfields > 9 && !fpr) { fpr = xtrystrdup (fields[9]); if (!fpr) { err = gpg_error_from_syserror (); goto leave; } } else if (!strcmp (fields[0], "uid") && nfields > 9) { int uidexpired, uidrevoked; set_expired_revoked (fields[1], &uidexpired, &uidrevoked); if (!append_to_uidinfo_list (&mboxes, fields[9], parse_timestamp (fields[5], NULL), expired || uidexpired, revoked || uidrevoked)) { err = gpg_error_from_syserror (); goto leave; } } } if (len < 0 || es_ferror (listing)) { err = gpg_error_from_syserror (); log_error ("error reading memory stream\n"); goto leave; } if (!fpr) { err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } if (r_fpr) { *r_fpr = fpr; fpr = NULL; } *r_mboxes = mboxes; mboxes = NULL; leave: xfree (fpr); free_uidinfo_list (mboxes); xfree (fields); es_free (line); xfree (argv); es_fclose (listing); return err; } /* Run gpg as a filter on KEY and write the output to a new stream * stored at R_NEWKEY. The new key will contain only the user id UID. * Returns 0 on success. Only one key is expected in KEY. If BINARY * is set the resulting key is returned as a binary (non-armored) * keyblock. */ gpg_error_t wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid, int binary) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; estream_t newkey; char *filterexp = NULL; *r_newkey = NULL; /* Open a memory stream. */ newkey = es_fopenmem (0, "w+b"); if (!newkey) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); return err; } /* Prefix the key with the MIME content type. */ if (!binary) es_fputs ("Content-Type: application/pgp-keys\n" "\n", newkey); filterexp = es_bsprintf ("keep-uid=-t uid= %s", uid); if (!filterexp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (opt.verbose < 2) ccparray_put (&ccp, "--quiet"); else ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--always-trust"); if (!binary) ccparray_put (&ccp, "--armor"); ccparray_put (&ccp, "--import-options=import-export"); ccparray_put (&ccp, "--import-filter"); ccparray_put (&ccp, filterexp); ccparray_put (&ccp, "--import"); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } debug_gpg_invocation (__func__, argv); err = gnupg_exec_tool_stream (opt.gpg_program, argv, key, NULL, newkey, key_status_cb, NULL); if (err) { log_error ("import/export failed: %s\n", gpg_strerror (err)); goto leave; } es_rewind (newkey); *r_newkey = newkey; newkey = NULL; leave: xfree (filterexp); xfree (argv); es_fclose (newkey); return err; } /* Put the ascii-armor around KEY and return that as a new estream * object at R_NEWKEY. Caller must make sure that KEY has been seeked * to the right position (usually by calling es_rewind). The * resulting NEWKEY has already been rewound. If PREFIX is not NULL, * its content is written to NEWKEY propr to the armor; this may be * used for MIME headers. */ gpg_error_t wks_armor_key (estream_t *r_newkey, estream_t key, const char *prefix) { gpg_error_t err; estream_t newkey; - struct b64state b64state; + gpgrt_b64state_t b64state; char buffer[4096]; size_t nread; *r_newkey = NULL; newkey = es_fopenmem (0, "w+b"); if (!newkey) { err = gpg_error_from_syserror (); return err; } if (prefix) es_fputs (prefix, newkey); - err = b64enc_start_es (&b64state, newkey, "PGP PUBLIC KEY BLOCK"); - if (err) - goto leave; + b64state = gpgrt_b64enc_start (newkey, "PGP PUBLIC KEY BLOCK"); + if (!b64state) + { + err = gpg_error_from_syserror (); + goto leave; + } do { nread = es_fread (buffer, 1, sizeof buffer, key); if (!nread) break; - err = b64enc_write (&b64state, buffer, nread); + err = gpgrt_b64enc_write (b64state, buffer, nread); if (err) goto leave; } while (!es_feof (key) && !es_ferror (key)); if (!es_feof (key) || es_ferror (key)) { err = gpg_error_from_syserror (); goto leave; } - err = b64enc_finish (&b64state); + err = gpgrt_b64enc_finish (b64state); if (err) goto leave; es_rewind (newkey); *r_newkey = newkey; newkey = NULL; leave: es_fclose (newkey); return err; } /* Run gpg to export the revocation certificates for ADDRSPEC. Add * them to KEY which is expected to be non-armored keyblock. */ gpg_error_t wks_find_add_revocs (estream_t key, const char *addrspec) { gpg_error_t err; ccparray_t ccp; const char **argv = NULL; char *filterexp = NULL; filterexp = es_bsprintf ("select=mbox= %s", addrspec); if (!filterexp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } ccparray_init (&ccp, 0); ccparray_put (&ccp, "--no-options"); if (opt.verbose < 2) ccparray_put (&ccp, "--quiet"); else ccparray_put (&ccp, "--verbose"); ccparray_put (&ccp, "--batch"); ccparray_put (&ccp, "--status-fd=2"); ccparray_put (&ccp, "--export-options=export-revocs"); ccparray_put (&ccp, "--export-filter"); ccparray_put (&ccp, filterexp); ccparray_put (&ccp, "--export"); ccparray_put (&ccp, addrspec); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } debug_gpg_invocation (__func__, argv); err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL, NULL, key, key_status_cb, NULL); if (err) { log_error ("exporting revocs failed: %s\n", gpg_strerror (err)); goto leave; } leave: xfree (filterexp); xfree (argv); return err; } /* Helper to write mail to the output(s). */ gpg_error_t wks_send_mime (mime_maker_t mime) { gpg_error_t err; estream_t mail; /* Without any option we take a short path. */ if (!opt.use_sendmail && !opt.output) { es_set_binary (es_stdout); return mime_maker_make (mime, es_stdout); } mail = es_fopenmem (0, "w+b"); if (!mail) { err = gpg_error_from_syserror (); return err; } err = mime_maker_make (mime, mail); if (!err && opt.output) { es_rewind (mail); err = send_mail_to_file (mail, opt.output); } if (!err && opt.use_sendmail) { es_rewind (mail); err = send_mail (mail); } es_fclose (mail); return err; } /* Parse the policy flags by reading them from STREAM and storing them * into FLAGS. If IGNORE_UNKNOWN is set unknown keywords are * ignored. */ gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown) { enum tokens { TOK_SUBMISSION_ADDRESS, TOK_MAILBOX_ONLY, TOK_DANE_ONLY, TOK_AUTH_SUBMIT, TOK_MAX_PENDING, TOK_PROTOCOL_VERSION }; static struct { const char *name; enum tokens token; } keywords[] = { { "submission-address", TOK_SUBMISSION_ADDRESS }, { "mailbox-only", TOK_MAILBOX_ONLY }, { "dane-only", TOK_DANE_ONLY }, { "auth-submit", TOK_AUTH_SUBMIT }, { "max-pending", TOK_MAX_PENDING }, { "protocol-version", TOK_PROTOCOL_VERSION } }; gpg_error_t err = 0; int lnr = 0; char line[1024]; char *p, *keyword, *value; int i, n; memset (flags, 0, sizeof *flags); while (es_fgets (line, DIM(line)-1, stream) ) { lnr++; n = strlen (line); if (!n || line[n-1] != '\n') { err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); break; } trim_trailing_spaces (line); /* Skip empty and comment lines. */ for (p=line; spacep (p); p++) ; if (!*p || *p == '#') continue; if (*p == ':') { err = gpg_error (GPG_ERR_SYNTAX); break; } keyword = p; value = NULL; if ((p = strchr (p, ':'))) { /* Colon found: Keyword with value. */ *p++ = 0; for (; spacep (p); p++) ; if (!*p) { err = gpg_error (GPG_ERR_MISSING_VALUE); break; } value = p; } for (i=0; i < DIM (keywords); i++) if (!ascii_strcasecmp (keywords[i].name, keyword)) break; if (!(i < DIM (keywords))) { if (ignore_unknown) continue; err = gpg_error (GPG_ERR_INV_NAME); break; } switch (keywords[i].token) { case TOK_SUBMISSION_ADDRESS: if (!value || !*value) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } xfree (flags->submission_address); flags->submission_address = xtrystrdup (value); if (!flags->submission_address) { err = gpg_error_from_syserror (); goto leave; } break; case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break; case TOK_DANE_ONLY: flags->dane_only = 1; break; case TOK_AUTH_SUBMIT: flags->auth_submit = 1; break; case TOK_MAX_PENDING: if (!value) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } /* FIXME: Define whether these are seconds, hours, or days * and decide whether to allow other units. */ flags->max_pending = atoi (value); break; case TOK_PROTOCOL_VERSION: if (!value) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } flags->protocol_version = atoi (value); break; } } if (!err && !es_feof (stream)) err = gpg_error_from_syserror (); leave: if (err) log_error ("error reading '%s', line %d: %s\n", es_fname_get (stream), lnr, gpg_strerror (err)); return err; } void wks_free_policy (policy_flags_t policy) { if (policy) { xfree (policy->submission_address); memset (policy, 0, sizeof *policy); } } /* Write the content of SRC to the new file FNAME. If FNAME is NULL * SRC is written to stdout. */ gpg_error_t wks_write_to_file (estream_t src, const char *fname) { gpg_error_t err; estream_t dst; char buffer[4096]; size_t nread, written; if (!fname) { dst = es_stdout; es_set_binary (es_stdout); } else { dst = es_fopen (fname, "wb"); if (!dst) return gpg_error_from_syserror (); } do { nread = es_fread (buffer, 1, sizeof buffer, src); if (!nread) break; written = es_fwrite (buffer, 1, nread, dst); if (written != nread) break; } while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst)); if (!es_feof (src) || es_ferror (src) || es_ferror (dst)) { err = gpg_error_from_syserror (); if (dst != es_stdout) { es_fclose (dst); gnupg_remove (fname); } return err; } if (dst != es_stdout && es_fclose (dst)) { err = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); return err; } return 0; } /* Return the filename and optionally the addrspec for USERID at * R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. If * HASH_ONLY is set only the has is returned at R_FNAME and no file is * created. */ gpg_error_t wks_fname_from_userid (const char *userid, int hash_only, char **r_fname, char **r_addrspec) { gpg_error_t err; char *addrspec = NULL; const char *domain; char *hash = NULL; const char *s; char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */ *r_fname = NULL; if (r_addrspec) *r_addrspec = NULL; addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { if (opt.verbose || hash_only) log_info ("\"%s\" is not a proper mail address\n", userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } domain = strchr (addrspec, '@'); log_assert (domain); domain++; if (strchr (domain, '/') || strchr (domain, '\\')) { log_info ("invalid domain detected ('%s')\n", domain); err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } /* Hash user ID and create filename. */ s = strchr (addrspec, '@'); log_assert (s); gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, addrspec, s - addrspec); hash = zb32_encode (shaxbuf, 8*20); if (!hash) { err = gpg_error_from_syserror (); goto leave; } if (hash_only) { *r_fname = hash; hash = NULL; err = 0; } else { *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); if (!*r_fname) err = gpg_error_from_syserror (); else err = 0; } leave: if (r_addrspec && addrspec) *r_addrspec = addrspec; else xfree (addrspec); xfree (hash); return err; } /* Compute the the full file name for the key with ADDRSPEC and return * it at R_FNAME. */ gpg_error_t wks_compute_hu_fname (char **r_fname, const char *addrspec) { gpg_error_t err; char *hash; const char *domain; char sha1buf[20]; char *fname; struct stat sb; *r_fname = NULL; domain = strchr (addrspec, '@'); if (!domain || !domain[1] || domain == addrspec) return gpg_error (GPG_ERR_INV_ARG); domain++; if (strchr (domain, '/') || strchr (domain, '\\')) { log_info ("invalid domain detected ('%s')\n", domain); return gpg_error (GPG_ERR_NOT_FOUND); } gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1); hash = zb32_encode (sha1buf, 8*20); if (!hash) return gpg_error_from_syserror (); /* Try to create missing directories below opt.directory. */ fname = make_filename_try (opt.directory, domain, NULL); if (fname && gnupg_stat (fname, &sb) && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) if (!gnupg_mkdir (fname, "-rwxr-xr-x") && opt.verbose) log_info ("directory '%s' created\n", fname); xfree (fname); fname = make_filename_try (opt.directory, domain, "hu", NULL); if (fname && gnupg_stat (fname, &sb) && gpg_err_code_from_syserror () == GPG_ERR_ENOENT) if (!gnupg_mkdir (fname, "-rwxr-xr-x") && opt.verbose) log_info ("directory '%s' created\n", fname); xfree (fname); /* Create the filename. */ fname = make_filename_try (opt.directory, domain, "hu", hash, NULL); err = fname? 0 : gpg_error_from_syserror (); if (err) xfree (fname); else *r_fname = fname; /* Okay. */ xfree (hash); return err; } /* Make sure that a policy file exists for addrspec. Directories must * already exist. */ static gpg_error_t ensure_policy_file (const char *addrspec) { gpg_err_code_t ec; gpg_error_t err; const char *domain; char *fname; estream_t fp; domain = strchr (addrspec, '@'); if (!domain || !domain[1] || domain == addrspec) return gpg_error (GPG_ERR_INV_ARG); domain++; if (strchr (domain, '/') || strchr (domain, '\\')) { log_info ("invalid domain detected ('%s')\n", domain); return gpg_error (GPG_ERR_NOT_FOUND); } /* Create the filename. */ fname = make_filename_try (opt.directory, domain, "policy", NULL); err = fname? 0 : gpg_error_from_syserror (); if (err) goto leave; /* First a quick check whether it already exists. */ if (!(ec = gnupg_access (fname, F_OK))) { err = 0; /* File already exists. */ goto leave; } err = gpg_error (ec); if (gpg_err_code (err) == GPG_ERR_ENOENT) err = 0; else { log_error ("domain %s: problem with '%s': %s\n", domain, fname, gpg_strerror (err)); goto leave; } /* Now create the file. */ fp = es_fopen (fname, "wxb"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_EEXIST) err = 0; /* Was created between the gnupg_access() and es_fopen(). */ else log_error ("domain %s: error creating '%s': %s\n", domain, fname, gpg_strerror (err)); goto leave; } es_fprintf (fp, "# Policy flags for domain %s\n", domain); if (es_ferror (fp) || es_fclose (fp)) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); goto leave; } if (opt.verbose) log_info ("policy file '%s' created\n", fname); /* Make sure the policy file world readable. */ if (gnupg_chmod (fname, "-rw-r--r--")) { err = gpg_error_from_syserror (); log_error ("can't set permissions of '%s': %s\n", fname, gpg_strerror (err)); goto leave; } leave: xfree (fname); return err; } /* Helper form wks_cmd_install_key. */ static gpg_error_t install_key_from_spec_file (const char *fname) { gpg_error_t err; estream_t fp; char *line = NULL; size_t linelen = 0; size_t maxlen = 2048; const char *fields[2]; unsigned int lnr = 0; if (!fname || !strcmp (fname, "")) fp = es_stdin; else fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } while (es_read_line (fp, &line, &linelen, &maxlen) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } lnr++; trim_spaces (line); if (!*line || *line == '#') continue; if (split_fields (line, fields, DIM(fields)) < 2) { log_error ("error reading '%s': syntax error at line %u\n", fname, lnr); continue; } err = wks_cmd_install_key (fields[0], fields[1]); if (err) goto leave; } if (es_ferror (fp)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } leave: if (fp != es_stdin) es_fclose (fp); es_free (line); return err; } /* The core of the code to install a key as a file. */ gpg_error_t wks_install_key_core (estream_t key, const char *addrspec) { gpg_error_t err; char *huname = NULL; /* Hash user ID and create filename. */ err = wks_compute_hu_fname (&huname, addrspec); if (err) goto leave; /* Now that wks_compute_hu_fname has created missing directories we * can create a policy file if it does not exist. */ err = ensure_policy_file (addrspec); if (err) goto leave; /* Publish. */ err = wks_write_to_file (key, huname); if (err) { log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err)); goto leave; } /* Make sure it is world readable. */ if (gnupg_chmod (huname, "-rw-r--r--")) log_error ("can't set permissions of '%s': %s\n", huname, gpg_strerror (gpg_err_code_from_syserror())); leave: xfree (huname); return err; } /* Install a single key into the WKD by reading FNAME and extracting * USERID. If USERID is NULL FNAME is expected to be a list of fpr * mbox lines and for each line the respective key will be * installed. */ gpg_error_t wks_cmd_install_key (const char *fname, const char *userid) { gpg_error_t err; KEYDB_SEARCH_DESC desc; estream_t fp = NULL; char *addrspec = NULL; char *fpr = NULL; uidinfo_list_t uidlist = NULL; uidinfo_list_t uid, thisuid; time_t thistime; int any; if (!userid) return install_key_from_spec_file (fname); addrspec = mailbox_from_userid (userid, 0); if (!addrspec) { log_error ("\"%s\" is not a proper mail address\n", userid); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } if (!classify_user_id (fname, &desc, 1) && desc.mode == KEYDB_SEARCH_MODE_FPR) { /* FNAME looks like a fingerprint. Get the key from the * standard keyring. */ err = wks_get_key (&fp, fname, addrspec, 0, 1); if (err) { log_error ("error getting key '%s' (uid='%s'): %s\n", fname, addrspec, gpg_strerror (err)); goto leave; } } else /* Take it from the file */ { fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } } /* List the key so that we can figure out the newest UID with the * requested addrspec. */ err = wks_list_key (fp, &fpr, &uidlist); if (err) { log_error ("error parsing key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } thistime = 0; thisuid = NULL; any = 0; for (uid = uidlist; uid; uid = uid->next) { if (!uid->mbox) continue; /* Should not happen anyway. */ if (ascii_strcasecmp (uid->mbox, addrspec)) continue; /* Not the requested addrspec. */ if (uid->expired) { if (opt.verbose) log_info ("ignoring expired user id '%s'\n", uid->uid); continue; } any = 1; if (uid->created > thistime) { thistime = uid->created; thisuid = uid; } } if (!thisuid) thisuid = uidlist; /* This is the case for a missing timestamp. */ if (!any) { log_error ("public key in '%s' has no mail address '%s'\n", fname, addrspec); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } if (opt.verbose) log_info ("using key with user id '%s'\n", thisuid->uid); { estream_t fp2; es_rewind (fp); err = wks_filter_uid (&fp2, fp, thisuid->uid, 1); if (err) { log_error ("error filtering key: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } es_fclose (fp); fp = fp2; } if (opt.add_revocs) { if (es_fseek (fp, 0, SEEK_END)) { err = gpg_error_from_syserror (); log_error ("error seeking stream: %s\n", gpg_strerror (err)); goto leave; } err = wks_find_add_revocs (fp, addrspec); if (err) { log_error ("error finding revocations for '%s': %s\n", addrspec, gpg_strerror (err)); goto leave; } es_rewind (fp); } err = wks_install_key_core (fp, addrspec); if (!opt.quiet) log_info ("key %s published for '%s'\n", fpr, addrspec); leave: free_uidinfo_list (uidlist); xfree (fpr); xfree (addrspec); es_fclose (fp); return err; } /* Remove the key with mail address in USERID. */ gpg_error_t wks_cmd_remove_key (const char *userid) { gpg_error_t err; char *addrspec = NULL; char *fname = NULL; err = wks_fname_from_userid (userid, 0, &fname, &addrspec); if (err) goto leave; if (gnupg_remove (fname)) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOENT) { if (!opt.quiet) log_info ("key for '%s' is not installed\n", addrspec); log_inc_errorcount (); err = 0; } else log_error ("error removing '%s': %s\n", fname, gpg_strerror (err)); goto leave; } if (opt.verbose) log_info ("key for '%s' removed\n", addrspec); err = 0; leave: xfree (fname); xfree (addrspec); return err; } /* Print the WKD hash for the user id to stdout. */ gpg_error_t wks_cmd_print_wkd_hash (const char *userid) { gpg_error_t err; char *addrspec, *fname; err = wks_fname_from_userid (userid, 1, &fname, &addrspec); if (err) return err; es_printf ("%s %s\n", fname, addrspec); xfree (fname); xfree (addrspec); return err; } /* Print the WKD URL for the user id to stdout. */ gpg_error_t wks_cmd_print_wkd_url (const char *userid) { gpg_error_t err; char *addrspec, *fname; char *domain; err = wks_fname_from_userid (userid, 1, &fname, &addrspec); if (err) return err; domain = strchr (addrspec, '@'); if (domain) *domain++ = 0; es_printf ("https://openpgpkey.%s/.well-known/openpgpkey/%s/hu/%s?l=%s\n", domain, domain, fname, addrspec); xfree (fname); xfree (addrspec); return err; }