diff --git a/agent/Makefile.am b/agent/Makefile.am index ce29462b2..87c9fa122 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -45,6 +45,7 @@ gpg_agent_SOURCES = \ cache.c \ trans.c \ findkey.c \ + sexp-secret.c \ pksign.c \ pkdecrypt.c \ genkey.c \ @@ -75,6 +76,7 @@ gpg_agent_DEPENDENCIES = $(resource_objs) gpg_protect_tool_SOURCES = \ protect-tool.c \ + sexp-secret.c \ protect.c cvt-openpgp.c gpg_protect_tool_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) \ diff --git a/agent/agent.h b/agent/agent.h index 90d8f5c73..857dffef6 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -642,4 +642,9 @@ extract_private_key (gcry_sexp_t s_key, int req_private_key_data, gcry_mpi_t *mpi_array, int arraysize, gcry_sexp_t *r_curve, gcry_sexp_t *r_flags); +/*-- sexp-secret.c --*/ +gpg_error_t fixup_when_ecc_private_key (unsigned char *buf, size_t *buflen_p); +gpg_error_t sexp_sscan_private_key (gcry_sexp_t *result, size_t *r_erroff, + unsigned char *buf); + #endif /*AGENT_H*/ diff --git a/agent/findkey.c b/agent/findkey.c index 7fb938b35..af42d6ece 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -968,7 +968,7 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, { gpg_error_t err; unsigned char *buf; - size_t len, buflen, erroff; + size_t len, erroff; gcry_sexp_t s_skey; nvc_t keymeta = NULL; char *desc_text_buffer = NULL; /* Used in case we extend DESC_TEXT. */ @@ -1117,10 +1117,10 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, return err; } - buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL); - err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); - wipememory (buf, buflen); + err = sexp_sscan_private_key (result, &erroff, buf); xfree (buf); + nvc_release (keymeta); + xfree (desc_text_buffer); if (err) { log_error ("failed to build S-Exp (off=%u): %s\n", @@ -1130,15 +1130,9 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, xfree (*r_passphrase); *r_passphrase = NULL; } - nvc_release (keymeta); - xfree (desc_text_buffer); - return err; } - *result = s_skey; - nvc_release (keymeta); - xfree (desc_text_buffer); - return 0; + return err; } diff --git a/agent/protect-tool.c b/agent/protect-tool.c index bcbe4588d..a95f418e6 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -372,7 +372,7 @@ read_and_protect (const char *fname) static void read_and_unprotect (ctrl_t ctrl, const char *fname) { - int rc; + gpg_error_t err; unsigned char *key; unsigned char *result; size_t resultlen; @@ -383,15 +383,15 @@ read_and_unprotect (ctrl_t ctrl, const char *fname) if (!key) return; - rc = agent_unprotect (ctrl, key, (pw=get_passphrase (1)), - protected_at, &result, &resultlen); + err = agent_unprotect (ctrl, key, (pw=get_passphrase (1)), + protected_at, &result, &resultlen); release_passphrase (pw); xfree (key); - if (rc) + if (err) { if (opt_status_msg) log_info ("[PROTECT-TOOL:] bad-passphrase\n"); - log_error ("unprotecting the key failed: %s\n", gpg_strerror (rc)); + log_error ("unprotecting the key failed: %s\n", gpg_strerror (err)); return; } if (opt.verbose) @@ -404,6 +404,12 @@ read_and_unprotect (ctrl_t ctrl, const char *fname) log_info ("key protection done at [unknown]\n"); } + err = fixup_when_ecc_private_key (result, &resultlen); + if (err) + { + log_error ("malformed key: %s\n", gpg_strerror (err)); + return; + } if (opt_armor) { char *p = make_advanced (result, resultlen); diff --git a/agent/sexp-secret.c b/agent/sexp-secret.c new file mode 100644 index 000000000..5f0bdfa2e --- /dev/null +++ b/agent/sexp-secret.c @@ -0,0 +1,128 @@ +/* sexp-secret.c - SEXP handling of the secret key + * Copyright (C) 2020 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 "agent.h" +#include "../common/sexp-parse.h" + +/* + * When it's for ECC, fixup private key part in the cannonical SEXP + * representation in BUF. If not ECC, do nothing. + */ +gpg_error_t +fixup_when_ecc_private_key (unsigned char *buf, size_t *buflen_p) +{ + const unsigned char *s; + size_t n; + size_t buflen = *buflen_p; + + s = buf; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "private-key")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!smatch (&s, n, "ecc")) + return 0; + + /* It's ECC */ + while (*s == '(') + { + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (n == 1 && *s == 'd') + { + unsigned char *s0; + size_t n0; + + s += n; + s0 = (unsigned char *)s; + n = snext (&s); + n0 = s - s0; + + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + else if ((n & 1) && !*s) + /* Detect wrongly added 0x00. */ + /* For all existing curves in libgcrypt-1.9 (so far), the + size of private part should be even. */ + { + size_t numsize; + + n--; + buflen--; + numsize = snprintf (s0, s-s0+1, "%u:", (unsigned int)n); + memmove (s0+numsize, s+1, buflen - (s - buf)); + memset (s0+numsize+buflen - (s - buf), 0, (n0 - numsize) + 1); + buflen -= (n0 - numsize); + s = s0+numsize+n; + *buflen_p = buflen; + } + else + s += n; + } + else + { + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + s += n; + } + if ( *s != ')' ) + return gpg_error (GPG_ERR_INV_SEXP); + s++; + } + if (*s != ')') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + + return 0; +} + +/* + * Scan BUF to get SEXP, put into RESULT. Error offset will be in the + * pointer at R_ERROFF. For ECC, the private part 'd' will be fixed + * up; That part may have 0x00 prefix of signed MPI encoding, which is + * incompatible to opaque MPI handling. + */ +gpg_error_t +sexp_sscan_private_key (gcry_sexp_t *result, size_t *r_erroff, + unsigned char *buf) +{ + gpg_error_t err; + size_t buflen, buflen0; + + buflen = buflen0 = gcry_sexp_canon_len (buf, 0, NULL, NULL); + err = fixup_when_ecc_private_key (buf, &buflen); + if (!err) + err = gcry_sexp_sscan (result, r_erroff, (char*)buf, buflen0); + wipememory (buf, buflen0); + + return err; +}