mirror of
git://git.gnupg.org/gnupg.git
synced 2024-11-10 21:38:50 +01:00
4c14bbf56f
* sm/minip12.c: Update from master. * sm/import.c (parse_p12): Pass NULL for curve. -- Over the last years we had a couple of changes not backported to 2.2. However, to support DFN p12 files and probably other p12 files we need to update the minip12.c module. Instead of picking commits we take the module verbatim, which is relatively easy because it was originally designed to be a standalone module. Summary of commits taken from master: sm: Improve pkcs#12 debug output. sm: Rework the PKCS#12 parser to support DFN issued keys. sm: Fix parsing encrypted data. sm: Do not print certain issuer not found diags in quiet mode. sm: Silence some output on --quiet sm: Replace all assert calls by log_assert. doc: Typo fixes in code comments sm: Add support to export ECC private keys. Detailed log messages for those commits: commit52f9e13c0c
sm: Improve pkcs#12 debug output. * sm/minip12.c (parse_shrouded_key_bag): Fix offset diagnostic. (parse_cert_bag): Ditto. (parse_bag_data): Remove debug output. Pass startoffset. Fix offset diagnostic. commita4e04375e8
sm: Rework the PKCS#12 parser to support DFN issued keys. * sm/minip12.c (struct p12_parse_ctx_s): New. Use this instead of passing several parameters to most functions. (parse_pag_data): Factor things out to ... parse_shrouded_key_bag): new. (parse_cert_bag): New. (parse_bag_data): New. (p12_parse): Setup the parse context. To support newer pkcs#12 files like those issued by the DFN we need to support another ordering of data elements. This rework reflects the P12 data structure a bit better than our old ad-hoc hacks. Tests could only be done with the certificate parts and not the encrypted private keys. GnuPG-bug-id: 6037 commit6c50834c09
sm: Fix parsing encrypted data. * sm/minip12.c (cram_octet_string): Finish when N==0. (parse_bag_encrypted_data): Support constructed data with multiple octet strings. GnuPG-bug-id: 5793 commita170f0e73f
sm: Do not print certain issuer not found diags in quiet mode. * sm/certchain.c (find_up_dirmngr): Print one diagnostic only in verbose mode. Do not print issuer not found diags in quiet mode. * sm/minip12.c (parse_bag_data): Add missing verbose condition. GnuPG-bug-id: 4757 commit615d2e4fb1
sm: Silence some output on --quiet * sm/encrypt.c (gpgsm_encrypt): Take care of --quiet. * sm/gpgsm.c: Include minip12.h. (set_debug): Call p12_set_verbosity. * sm/import.c (parse_p12): Dump keygrip only in debug mode. * sm/minip12.c (opt_verbose, p12_set_verbosity): New. (parse_bag_encrypted_data): Print info messages only in verbose mode. GnuPG-bug-id: 4757 commit9ee975d588
gpgsm: Replace all assert calls by log_assert. commit9bc9d0818b
doc: Typo fixes in code comments commit5da6925a33
sm: Add support to export ECC private keys. * sm/minip12.c [TEST]: Remove test code. Include util.h, tlv.h. and openpgpdefs.h. Remove the class and tag constants and replace them by those from tlv.h. (builder_add_oid, builder_add_mpi): New. (build_key_sequence): Rename to ... (build_rsa_key_sequence): this. (build_ecc_key_sequence): New. (p12_build): Call RSA or ECC builder. (p12_raw_build): Ditto. * sm/export.c (gpgsm_p12_export): Use correct armor header for ECC. (sexp_to_kparms): Support ECC. GnuPG-bug-id: 4921
942 lines
25 KiB
C
942 lines
25 KiB
C
/* import.c - Import certificates
|
||
* Copyright (C) 2001, 2003, 2004, 2009, 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 <https://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include <config.h>
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include <time.h>
|
||
#include <assert.h>
|
||
#include <unistd.h>
|
||
|
||
#include "gpgsm.h"
|
||
#include <gcrypt.h>
|
||
#include <ksba.h>
|
||
|
||
#include "keydb.h"
|
||
#include "../common/exechelp.h"
|
||
#include "../common/i18n.h"
|
||
#include "../common/sysutils.h"
|
||
#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
|
||
#include "../common/membuf.h"
|
||
#include "minip12.h"
|
||
|
||
/* The arbitrary limit of one PKCS#12 object. */
|
||
#define MAX_P12OBJ_SIZE 128 /*kb*/
|
||
|
||
|
||
struct stats_s {
|
||
unsigned long count;
|
||
unsigned long imported;
|
||
unsigned long unchanged;
|
||
unsigned long not_imported;
|
||
unsigned long secret_read;
|
||
unsigned long secret_imported;
|
||
unsigned long secret_dups;
|
||
};
|
||
|
||
|
||
struct rsa_secret_key_s
|
||
{
|
||
gcry_mpi_t n; /* public modulus */
|
||
gcry_mpi_t e; /* public exponent */
|
||
gcry_mpi_t d; /* exponent */
|
||
gcry_mpi_t p; /* prime p. */
|
||
gcry_mpi_t q; /* prime q. */
|
||
gcry_mpi_t u; /* inverse of p mod q. */
|
||
};
|
||
|
||
|
||
static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader,
|
||
struct stats_s *stats);
|
||
|
||
|
||
|
||
static void
|
||
print_imported_status (ctrl_t ctrl, ksba_cert_t cert, int new_cert)
|
||
{
|
||
char *fpr;
|
||
|
||
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
|
||
if (new_cert)
|
||
gpgsm_status2 (ctrl, STATUS_IMPORTED, fpr, "[X.509]", NULL);
|
||
|
||
gpgsm_status2 (ctrl, STATUS_IMPORT_OK,
|
||
new_cert? "1":"0", fpr, NULL);
|
||
|
||
xfree (fpr);
|
||
}
|
||
|
||
|
||
/* Print an IMPORT_PROBLEM status. REASON is one of:
|
||
0 := "No specific reason given".
|
||
1 := "Invalid Certificate".
|
||
2 := "Issuer Certificate missing".
|
||
3 := "Certificate Chain too long".
|
||
4 := "Error storing certificate".
|
||
*/
|
||
static void
|
||
print_import_problem (ctrl_t ctrl, ksba_cert_t cert, int reason)
|
||
{
|
||
char *fpr = NULL;
|
||
char buf[25];
|
||
int i;
|
||
|
||
sprintf (buf, "%d", reason);
|
||
if (cert)
|
||
{
|
||
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
|
||
/* detetect an error (all high) value */
|
||
for (i=0; fpr[i] == 'F'; i++)
|
||
;
|
||
if (!fpr[i])
|
||
{
|
||
xfree (fpr);
|
||
fpr = NULL;
|
||
}
|
||
}
|
||
gpgsm_status2 (ctrl, STATUS_IMPORT_PROBLEM, buf, fpr, NULL);
|
||
xfree (fpr);
|
||
}
|
||
|
||
|
||
void
|
||
print_imported_summary (ctrl_t ctrl, struct stats_s *stats)
|
||
{
|
||
char buf[14*25];
|
||
|
||
if (!opt.quiet)
|
||
{
|
||
log_info (_("total number processed: %lu\n"), stats->count);
|
||
if (stats->imported)
|
||
{
|
||
log_info (_(" imported: %lu"), stats->imported );
|
||
log_printf ("\n");
|
||
}
|
||
if (stats->unchanged)
|
||
log_info (_(" unchanged: %lu\n"), stats->unchanged);
|
||
if (stats->secret_read)
|
||
log_info (_(" secret keys read: %lu\n"), stats->secret_read );
|
||
if (stats->secret_imported)
|
||
log_info (_(" secret keys imported: %lu\n"), stats->secret_imported );
|
||
if (stats->secret_dups)
|
||
log_info (_(" secret keys unchanged: %lu\n"), stats->secret_dups );
|
||
if (stats->not_imported)
|
||
log_info (_(" not imported: %lu\n"), stats->not_imported);
|
||
}
|
||
|
||
sprintf(buf, "%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
|
||
stats->count,
|
||
0l /*stats->no_user_id*/,
|
||
stats->imported,
|
||
0l /*stats->imported_rsa*/,
|
||
stats->unchanged,
|
||
0l /*stats->n_uids*/,
|
||
0l /*stats->n_subk*/,
|
||
0l /*stats->n_sigs*/,
|
||
0l /*stats->n_revoc*/,
|
||
stats->secret_read,
|
||
stats->secret_imported,
|
||
stats->secret_dups,
|
||
0l /*stats->skipped_new_keys*/,
|
||
stats->not_imported
|
||
);
|
||
gpgsm_status (ctrl, STATUS_IMPORT_RES, buf);
|
||
}
|
||
|
||
|
||
|
||
static void
|
||
check_and_store (ctrl_t ctrl, struct stats_s *stats,
|
||
ksba_cert_t cert, int depth)
|
||
{
|
||
int rc;
|
||
|
||
if (stats)
|
||
stats->count++;
|
||
if ( depth >= 50 )
|
||
{
|
||
log_error (_("certificate chain too long\n"));
|
||
if (stats)
|
||
stats->not_imported++;
|
||
print_import_problem (ctrl, cert, 3);
|
||
return;
|
||
}
|
||
|
||
/* Some basic checks, but don't care about missing certificates;
|
||
this is so that we are able to import entire certificate chains
|
||
w/o requiring a special order (i.e. root-CA first). This used
|
||
to be different but because gpgsm_verify even imports
|
||
certificates without any checks, it doesn't matter much and the
|
||
code gets much cleaner. A housekeeping function to remove
|
||
certificates w/o an anchor would be nice, though.
|
||
|
||
Optionally we do a full validation in addition to the basic test.
|
||
*/
|
||
rc = gpgsm_basic_cert_check (ctrl, cert);
|
||
if (!rc && ctrl->with_validation)
|
||
rc = gpgsm_validate_chain (ctrl, cert, "", NULL, 0, NULL, 0, NULL);
|
||
if (!rc || (!ctrl->with_validation
|
||
&& (gpg_err_code (rc) == GPG_ERR_MISSING_CERT
|
||
|| gpg_err_code (rc) == GPG_ERR_MISSING_ISSUER_CERT)))
|
||
{
|
||
int existed;
|
||
|
||
if (!keydb_store_cert (ctrl, cert, 0, &existed))
|
||
{
|
||
ksba_cert_t next = NULL;
|
||
|
||
if (!existed)
|
||
{
|
||
print_imported_status (ctrl, cert, 1);
|
||
if (stats)
|
||
stats->imported++;
|
||
}
|
||
else
|
||
{
|
||
print_imported_status (ctrl, cert, 0);
|
||
if (stats)
|
||
stats->unchanged++;
|
||
}
|
||
|
||
if (opt.verbose > 1 && existed)
|
||
{
|
||
if (depth)
|
||
log_info ("issuer certificate already in DB\n");
|
||
else
|
||
log_info ("certificate already in DB\n");
|
||
}
|
||
else if (opt.verbose && !existed)
|
||
{
|
||
if (depth)
|
||
log_info ("issuer certificate imported\n");
|
||
else
|
||
log_info ("certificate imported\n");
|
||
}
|
||
|
||
/* Now lets walk up the chain and import all certificates up
|
||
the chain. This is required in case we already stored
|
||
parent certificates in the ephemeral keybox. Do not
|
||
update the statistics, though. */
|
||
if (!gpgsm_walk_cert_chain (ctrl, cert, &next))
|
||
{
|
||
check_and_store (ctrl, NULL, next, depth+1);
|
||
ksba_cert_release (next);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
log_error (_("error storing certificate\n"));
|
||
if (stats)
|
||
stats->not_imported++;
|
||
print_import_problem (ctrl, cert, 4);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
log_error (_("basic certificate checks failed - not imported\n"));
|
||
if (stats)
|
||
stats->not_imported++;
|
||
/* We keep the test for GPG_ERR_MISSING_CERT only in case
|
||
GPG_ERR_MISSING_CERT has been used instead of the newer
|
||
GPG_ERR_MISSING_ISSUER_CERT. */
|
||
print_import_problem
|
||
(ctrl, cert,
|
||
gpg_err_code (rc) == GPG_ERR_MISSING_ISSUER_CERT? 2 :
|
||
gpg_err_code (rc) == GPG_ERR_MISSING_CERT? 2 :
|
||
gpg_err_code (rc) == GPG_ERR_BAD_CERT? 1 : 0);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
static int
|
||
import_one (ctrl_t ctrl, struct stats_s *stats, int in_fd)
|
||
{
|
||
int rc;
|
||
gnupg_ksba_io_t b64reader = NULL;
|
||
ksba_reader_t reader;
|
||
ksba_cert_t cert = NULL;
|
||
ksba_cms_t cms = NULL;
|
||
estream_t fp = NULL;
|
||
ksba_content_type_t ct;
|
||
int any = 0;
|
||
|
||
fp = es_fdopen_nc (in_fd, "rb");
|
||
if (!fp)
|
||
{
|
||
rc = gpg_error_from_syserror ();
|
||
log_error ("fdopen() failed: %s\n", strerror (errno));
|
||
goto leave;
|
||
}
|
||
|
||
rc = gnupg_ksba_create_reader
|
||
(&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0)
|
||
| (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0)
|
||
| (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)
|
||
| GNUPG_KSBA_IO_MULTIPEM),
|
||
fp, &reader);
|
||
if (rc)
|
||
{
|
||
log_error ("can't create reader: %s\n", gpg_strerror (rc));
|
||
goto leave;
|
||
}
|
||
|
||
|
||
/* We need to loop here to handle multiple PEM objects in one
|
||
file. */
|
||
do
|
||
{
|
||
ksba_cms_release (cms); cms = NULL;
|
||
ksba_cert_release (cert); cert = NULL;
|
||
|
||
ct = ksba_cms_identify (reader);
|
||
if (ct == KSBA_CT_SIGNED_DATA)
|
||
{ /* This is probably a signed-only message - import the certs */
|
||
ksba_stop_reason_t stopreason;
|
||
int i;
|
||
|
||
rc = ksba_cms_new (&cms);
|
||
if (rc)
|
||
goto leave;
|
||
|
||
rc = ksba_cms_set_reader_writer (cms, reader, NULL);
|
||
if (rc)
|
||
{
|
||
log_error ("ksba_cms_set_reader_writer failed: %s\n",
|
||
gpg_strerror (rc));
|
||
goto leave;
|
||
}
|
||
|
||
do
|
||
{
|
||
rc = ksba_cms_parse (cms, &stopreason);
|
||
if (rc)
|
||
{
|
||
log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc));
|
||
goto leave;
|
||
}
|
||
|
||
if (stopreason == KSBA_SR_BEGIN_DATA)
|
||
log_info ("not a certs-only message\n");
|
||
}
|
||
while (stopreason != KSBA_SR_READY);
|
||
|
||
for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++)
|
||
{
|
||
check_and_store (ctrl, stats, cert, 0);
|
||
ksba_cert_release (cert);
|
||
cert = NULL;
|
||
}
|
||
if (!i)
|
||
log_error ("no certificate found\n");
|
||
else
|
||
any = 1;
|
||
}
|
||
else if (ct == KSBA_CT_PKCS12)
|
||
{
|
||
/* This seems to be a pkcs12 message. */
|
||
rc = parse_p12 (ctrl, reader, stats);
|
||
if (!rc)
|
||
any = 1;
|
||
}
|
||
else if (ct == KSBA_CT_NONE)
|
||
{ /* Failed to identify this message - assume a certificate */
|
||
|
||
rc = ksba_cert_new (&cert);
|
||
if (rc)
|
||
goto leave;
|
||
|
||
rc = ksba_cert_read_der (cert, reader);
|
||
if (rc)
|
||
goto leave;
|
||
|
||
check_and_store (ctrl, stats, cert, 0);
|
||
any = 1;
|
||
}
|
||
else
|
||
{
|
||
log_error ("can't extract certificates from input\n");
|
||
rc = gpg_error (GPG_ERR_NO_DATA);
|
||
}
|
||
|
||
ksba_reader_clear (reader, NULL, NULL);
|
||
}
|
||
while (!gnupg_ksba_reader_eof_seen (b64reader));
|
||
|
||
leave:
|
||
if (any && gpg_err_code (rc) == GPG_ERR_EOF)
|
||
rc = 0;
|
||
ksba_cms_release (cms);
|
||
ksba_cert_release (cert);
|
||
gnupg_ksba_destroy_reader (b64reader);
|
||
es_fclose (fp);
|
||
return rc;
|
||
}
|
||
|
||
|
||
|
||
/* Re-import certifciates. IN_FD is a list of linefeed delimited
|
||
fingerprints t re-import. The actual re-import is done by clearing
|
||
the ephemeral flag. */
|
||
static int
|
||
reimport_one (ctrl_t ctrl, struct stats_s *stats, int in_fd)
|
||
{
|
||
gpg_error_t err = 0;
|
||
estream_t fp = NULL;
|
||
char line[100]; /* Sufficient for a fingerprint. */
|
||
KEYDB_HANDLE kh;
|
||
KEYDB_SEARCH_DESC desc;
|
||
ksba_cert_t cert = NULL;
|
||
unsigned int flags;
|
||
|
||
kh = keydb_new ();
|
||
if (!kh)
|
||
{
|
||
err = gpg_error (GPG_ERR_ENOMEM);;
|
||
log_error (_("failed to allocate keyDB handle\n"));
|
||
goto leave;
|
||
}
|
||
keydb_set_ephemeral (kh, 1);
|
||
|
||
fp = es_fdopen_nc (in_fd, "r");
|
||
if (!fp)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
log_error ("es_fdopen(%d) failed: %s\n", in_fd, gpg_strerror (err));
|
||
goto leave;
|
||
}
|
||
|
||
while (es_fgets (line, DIM(line)-1, fp) )
|
||
{
|
||
if (*line && line[strlen(line)-1] != '\n')
|
||
{
|
||
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
|
||
goto leave;
|
||
}
|
||
trim_spaces (line);
|
||
if (!*line)
|
||
continue;
|
||
|
||
stats->count++;
|
||
|
||
err = classify_user_id (line, &desc, 0);
|
||
if (err)
|
||
{
|
||
print_import_problem (ctrl, NULL, 0);
|
||
stats->not_imported++;
|
||
continue;
|
||
}
|
||
|
||
keydb_search_reset (kh);
|
||
err = keydb_search (ctrl, kh, &desc, 1);
|
||
if (err)
|
||
{
|
||
print_import_problem (ctrl, NULL, 0);
|
||
stats->not_imported++;
|
||
continue;
|
||
}
|
||
|
||
ksba_cert_release (cert);
|
||
cert = NULL;
|
||
err = keydb_get_cert (kh, &cert);
|
||
if (err)
|
||
{
|
||
log_error ("keydb_get_cert() failed: %s\n", gpg_strerror (err));
|
||
print_import_problem (ctrl, NULL, 1);
|
||
stats->not_imported++;
|
||
continue;
|
||
}
|
||
|
||
err = keydb_get_flags (kh, KEYBOX_FLAG_BLOB, 0, &flags);
|
||
if (err)
|
||
{
|
||
log_error (_("error getting stored flags: %s\n"), gpg_strerror (err));
|
||
print_imported_status (ctrl, cert, 0);
|
||
stats->not_imported++;
|
||
continue;
|
||
}
|
||
if ( !(flags & KEYBOX_FLAG_BLOB_EPHEMERAL) )
|
||
{
|
||
print_imported_status (ctrl, cert, 0);
|
||
stats->unchanged++;
|
||
continue;
|
||
}
|
||
|
||
err = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0,
|
||
KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
|
||
if (err)
|
||
{
|
||
log_error ("clearing ephemeral flag failed: %s\n",
|
||
gpg_strerror (err));
|
||
print_import_problem (ctrl, cert, 0);
|
||
stats->not_imported++;
|
||
continue;
|
||
}
|
||
|
||
print_imported_status (ctrl, cert, 1);
|
||
stats->imported++;
|
||
}
|
||
err = 0;
|
||
if (es_ferror (fp))
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
log_error ("error reading fd %d: %s\n", in_fd, gpg_strerror (err));
|
||
goto leave;
|
||
}
|
||
|
||
leave:
|
||
ksba_cert_release (cert);
|
||
keydb_release (kh);
|
||
es_fclose (fp);
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
int
|
||
gpgsm_import (ctrl_t ctrl, int in_fd, int reimport_mode)
|
||
{
|
||
int rc;
|
||
struct stats_s stats;
|
||
|
||
memset (&stats, 0, sizeof stats);
|
||
if (reimport_mode)
|
||
rc = reimport_one (ctrl, &stats, in_fd);
|
||
else
|
||
rc = import_one (ctrl, &stats, in_fd);
|
||
print_imported_summary (ctrl, &stats);
|
||
/* If we never printed an error message do it now so that a command
|
||
line invocation will return with an error (log_error keeps a
|
||
global errorcount) */
|
||
if (rc && !log_get_errorcount (0))
|
||
log_error (_("error importing certificate: %s\n"), gpg_strerror (rc));
|
||
return rc;
|
||
}
|
||
|
||
|
||
int
|
||
gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files,
|
||
int (*of)(const char *fname))
|
||
{
|
||
int rc = 0;
|
||
struct stats_s stats;
|
||
|
||
memset (&stats, 0, sizeof stats);
|
||
|
||
if (!nfiles)
|
||
rc = import_one (ctrl, &stats, 0);
|
||
else
|
||
{
|
||
for (; nfiles && !rc ; nfiles--, files++)
|
||
{
|
||
int fd = of (*files);
|
||
rc = import_one (ctrl, &stats, fd);
|
||
close (fd);
|
||
if (rc == -1)
|
||
rc = 0;
|
||
}
|
||
}
|
||
print_imported_summary (ctrl, &stats);
|
||
/* If we never printed an error message do it now so that a command
|
||
line invocation will return with an error (log_error keeps a
|
||
global errorcount) */
|
||
if (rc && !log_get_errorcount (0))
|
||
log_error (_("error importing certificate: %s\n"), gpg_strerror (rc));
|
||
return rc;
|
||
}
|
||
|
||
|
||
/* Check that the RSA secret key SKEY is valid. Swap parameters to
|
||
the libgcrypt standard. */
|
||
static gpg_error_t
|
||
rsa_key_check (struct rsa_secret_key_s *skey)
|
||
{
|
||
int err = 0;
|
||
gcry_mpi_t t = gcry_mpi_snew (0);
|
||
gcry_mpi_t t1 = gcry_mpi_snew (0);
|
||
gcry_mpi_t t2 = gcry_mpi_snew (0);
|
||
gcry_mpi_t phi = gcry_mpi_snew (0);
|
||
|
||
/* Check that n == p * q. */
|
||
gcry_mpi_mul (t, skey->p, skey->q);
|
||
if (gcry_mpi_cmp( t, skey->n) )
|
||
{
|
||
log_error ("RSA oops: n != p * q\n");
|
||
err++;
|
||
}
|
||
|
||
/* Check that p is less than q. */
|
||
if (gcry_mpi_cmp (skey->p, skey->q) > 0)
|
||
{
|
||
gcry_mpi_t tmp;
|
||
|
||
log_info ("swapping secret primes\n");
|
||
tmp = gcry_mpi_copy (skey->p);
|
||
gcry_mpi_set (skey->p, skey->q);
|
||
gcry_mpi_set (skey->q, tmp);
|
||
gcry_mpi_release (tmp);
|
||
/* Recompute u. */
|
||
gcry_mpi_invm (skey->u, skey->p, skey->q);
|
||
}
|
||
|
||
/* Check that e divides neither p-1 nor q-1. */
|
||
gcry_mpi_sub_ui (t, skey->p, 1 );
|
||
gcry_mpi_div (NULL, t, t, skey->e, 0);
|
||
if (!gcry_mpi_cmp_ui( t, 0) )
|
||
{
|
||
log_error ("RSA oops: e divides p-1\n");
|
||
err++;
|
||
}
|
||
gcry_mpi_sub_ui (t, skey->q, 1);
|
||
gcry_mpi_div (NULL, t, t, skey->e, 0);
|
||
if (!gcry_mpi_cmp_ui( t, 0))
|
||
{
|
||
log_info ("RSA oops: e divides q-1\n" );
|
||
err++;
|
||
}
|
||
|
||
/* Check that d is correct. */
|
||
gcry_mpi_sub_ui (t1, skey->p, 1);
|
||
gcry_mpi_sub_ui (t2, skey->q, 1);
|
||
gcry_mpi_mul (phi, t1, t2);
|
||
gcry_mpi_invm (t, skey->e, phi);
|
||
if (gcry_mpi_cmp (t, skey->d))
|
||
{
|
||
/* No: try universal exponent. */
|
||
gcry_mpi_gcd (t, t1, t2);
|
||
gcry_mpi_div (t, NULL, phi, t, 0);
|
||
gcry_mpi_invm (t, skey->e, t);
|
||
if (gcry_mpi_cmp (t, skey->d))
|
||
{
|
||
log_error ("RSA oops: bad secret exponent\n");
|
||
err++;
|
||
}
|
||
}
|
||
|
||
/* Check for correctness of u. */
|
||
gcry_mpi_invm (t, skey->p, skey->q);
|
||
if (gcry_mpi_cmp (t, skey->u))
|
||
{
|
||
log_info ("RSA oops: bad u parameter\n");
|
||
err++;
|
||
}
|
||
|
||
if (err)
|
||
log_info ("RSA secret key check failed\n");
|
||
|
||
gcry_mpi_release (t);
|
||
gcry_mpi_release (t1);
|
||
gcry_mpi_release (t2);
|
||
gcry_mpi_release (phi);
|
||
|
||
return err? gpg_error (GPG_ERR_BAD_SECKEY):0;
|
||
}
|
||
|
||
|
||
/* Object passed to store_cert_cb. */
|
||
struct store_cert_parm_s
|
||
{
|
||
gpg_error_t err; /* First error seen. */
|
||
struct stats_s *stats; /* The stats object. */
|
||
ctrl_t ctrl; /* The control object. */
|
||
};
|
||
|
||
/* Helper to store the DER encoded certificate CERTDATA of length
|
||
CERTDATALEN. */
|
||
static void
|
||
store_cert_cb (void *opaque,
|
||
const unsigned char *certdata, size_t certdatalen)
|
||
{
|
||
struct store_cert_parm_s *parm = opaque;
|
||
gpg_error_t err;
|
||
ksba_cert_t cert;
|
||
|
||
err = ksba_cert_new (&cert);
|
||
if (err)
|
||
{
|
||
if (!parm->err)
|
||
parm->err = err;
|
||
return;
|
||
}
|
||
|
||
err = ksba_cert_init_from_mem (cert, certdata, certdatalen);
|
||
if (err)
|
||
{
|
||
log_error ("failed to parse a certificate: %s\n", gpg_strerror (err));
|
||
if (!parm->err)
|
||
parm->err = err;
|
||
}
|
||
else
|
||
check_and_store (parm->ctrl, parm->stats, cert, 0);
|
||
ksba_cert_release (cert);
|
||
}
|
||
|
||
|
||
/* Assume that the reader is at a pkcs#12 message and try to import
|
||
certificates from that stupid format. We will transfer secret
|
||
keys to the agent. */
|
||
static gpg_error_t
|
||
parse_p12 (ctrl_t ctrl, ksba_reader_t reader, struct stats_s *stats)
|
||
{
|
||
gpg_error_t err = 0;
|
||
char buffer[1024];
|
||
size_t ntotal, nread;
|
||
membuf_t p12mbuf;
|
||
char *p12buffer = NULL;
|
||
size_t p12buflen;
|
||
size_t p12bufoff;
|
||
gcry_mpi_t *kparms = NULL;
|
||
struct rsa_secret_key_s sk;
|
||
char *passphrase = NULL;
|
||
unsigned char *key = NULL;
|
||
size_t keylen;
|
||
void *kek = NULL;
|
||
size_t keklen;
|
||
unsigned char *wrappedkey = NULL;
|
||
size_t wrappedkeylen;
|
||
gcry_cipher_hd_t cipherhd = NULL;
|
||
gcry_sexp_t s_key = NULL;
|
||
unsigned char grip[20];
|
||
int bad_pass = 0;
|
||
int i;
|
||
struct store_cert_parm_s store_cert_parm;
|
||
|
||
memset (&store_cert_parm, 0, sizeof store_cert_parm);
|
||
store_cert_parm.ctrl = ctrl;
|
||
store_cert_parm.stats = stats;
|
||
|
||
init_membuf (&p12mbuf, 4096);
|
||
ntotal = 0;
|
||
while (!(err = ksba_reader_read (reader, buffer, sizeof buffer, &nread)))
|
||
{
|
||
if (ntotal >= MAX_P12OBJ_SIZE*1024)
|
||
{
|
||
/* Arbitrary limit to avoid DoS attacks. */
|
||
err = gpg_error (GPG_ERR_TOO_LARGE);
|
||
log_error ("pkcs#12 object is larger than %dk\n", MAX_P12OBJ_SIZE);
|
||
break;
|
||
}
|
||
put_membuf (&p12mbuf, buffer, nread);
|
||
ntotal += nread;
|
||
}
|
||
if (gpg_err_code (err) == GPG_ERR_EOF)
|
||
err = 0;
|
||
if (!err)
|
||
{
|
||
p12buffer = get_membuf (&p12mbuf, &p12buflen);
|
||
if (!p12buffer)
|
||
err = gpg_error_from_syserror ();
|
||
}
|
||
if (err)
|
||
{
|
||
log_error (_("error reading input: %s\n"), gpg_strerror (err));
|
||
goto leave;
|
||
}
|
||
|
||
/* GnuPG 2.0.4 accidentally created binary P12 files with the string
|
||
"The passphrase is %s encoded.\n\n" prepended to the ASN.1 data.
|
||
We fix that here. */
|
||
if (p12buflen > 29 && !memcmp (p12buffer, "The passphrase is ", 18))
|
||
{
|
||
for (p12bufoff=18;
|
||
p12bufoff < p12buflen && p12buffer[p12bufoff] != '\n';
|
||
p12bufoff++)
|
||
;
|
||
p12bufoff++;
|
||
if (p12bufoff < p12buflen && p12buffer[p12bufoff] == '\n')
|
||
p12bufoff++;
|
||
}
|
||
else
|
||
p12bufoff = 0;
|
||
|
||
|
||
err = gpgsm_agent_ask_passphrase
|
||
(ctrl,
|
||
i18n_utf8 (N_("Please enter the passphrase to unprotect the PKCS#12 object.")),
|
||
0, &passphrase);
|
||
if (err)
|
||
goto leave;
|
||
|
||
kparms = p12_parse (p12buffer + p12bufoff, p12buflen - p12bufoff,
|
||
passphrase, store_cert_cb, &store_cert_parm,
|
||
&bad_pass, NULL);
|
||
|
||
xfree (passphrase);
|
||
passphrase = NULL;
|
||
|
||
if (!kparms)
|
||
{
|
||
log_error ("error parsing or decrypting the PKCS#12 file\n");
|
||
err = gpg_error (GPG_ERR_INV_OBJ);
|
||
goto leave;
|
||
}
|
||
|
||
/* print_mpi (" n", kparms[0]); */
|
||
/* print_mpi (" e", kparms[1]); */
|
||
/* print_mpi (" d", kparms[2]); */
|
||
/* print_mpi (" p", kparms[3]); */
|
||
/* print_mpi (" q", kparms[4]); */
|
||
/* print_mpi ("dmp1", kparms[5]); */
|
||
/* print_mpi ("dmq1", kparms[6]); */
|
||
/* print_mpi (" u", kparms[7]); */
|
||
|
||
sk.n = kparms[0];
|
||
sk.e = kparms[1];
|
||
sk.d = kparms[2];
|
||
sk.q = kparms[3];
|
||
sk.p = kparms[4];
|
||
sk.u = kparms[7];
|
||
err = rsa_key_check (&sk);
|
||
if (err)
|
||
goto leave;
|
||
/* print_mpi (" n", sk.n); */
|
||
/* print_mpi (" e", sk.e); */
|
||
/* print_mpi (" d", sk.d); */
|
||
/* print_mpi (" p", sk.p); */
|
||
/* print_mpi (" q", sk.q); */
|
||
/* print_mpi (" u", sk.u); */
|
||
|
||
/* Create an S-expression from the parameters. */
|
||
err = gcry_sexp_build (&s_key, NULL,
|
||
"(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
|
||
sk.n, sk.e, sk.d, sk.p, sk.q, sk.u, NULL);
|
||
for (i=0; i < 8; i++)
|
||
gcry_mpi_release (kparms[i]);
|
||
gcry_free (kparms);
|
||
kparms = NULL;
|
||
if (err)
|
||
{
|
||
log_error ("failed to create S-expression from key: %s\n",
|
||
gpg_strerror (err));
|
||
goto leave;
|
||
}
|
||
|
||
/* Compute the keygrip. */
|
||
if (!gcry_pk_get_keygrip (s_key, grip))
|
||
{
|
||
err = gpg_error (GPG_ERR_GENERAL);
|
||
log_error ("can't calculate keygrip\n");
|
||
goto leave;
|
||
}
|
||
if (DBG_X509)
|
||
log_printhex (grip, 20, "keygrip=");
|
||
|
||
/* Convert to canonical encoding using a function which pads it to a
|
||
multiple of 64 bits. We need this padding for AESWRAP. */
|
||
err = make_canon_sexp_pad (s_key, 1, &key, &keylen);
|
||
if (err)
|
||
{
|
||
log_error ("error creating canonical S-expression\n");
|
||
goto leave;
|
||
}
|
||
gcry_sexp_release (s_key);
|
||
s_key = NULL;
|
||
|
||
/* Get the current KEK. */
|
||
err = gpgsm_agent_keywrap_key (ctrl, 0, &kek, &keklen);
|
||
if (err)
|
||
{
|
||
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
|
||
goto leave;
|
||
}
|
||
|
||
/* Wrap the key. */
|
||
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
|
||
GCRY_CIPHER_MODE_AESWRAP, 0);
|
||
if (err)
|
||
goto leave;
|
||
err = gcry_cipher_setkey (cipherhd, kek, keklen);
|
||
if (err)
|
||
goto leave;
|
||
xfree (kek);
|
||
kek = NULL;
|
||
|
||
wrappedkeylen = keylen + 8;
|
||
wrappedkey = xtrymalloc (wrappedkeylen);
|
||
if (!wrappedkey)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
|
||
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
|
||
if (err)
|
||
goto leave;
|
||
xfree (key);
|
||
key = NULL;
|
||
gcry_cipher_close (cipherhd);
|
||
cipherhd = NULL;
|
||
|
||
/* Send the wrapped key to the agent. */
|
||
err = gpgsm_agent_import_key (ctrl, wrappedkey, wrappedkeylen);
|
||
if (!err)
|
||
{
|
||
stats->count++;
|
||
stats->secret_read++;
|
||
stats->secret_imported++;
|
||
}
|
||
else if ( gpg_err_code (err) == GPG_ERR_EEXIST )
|
||
{
|
||
err = 0;
|
||
stats->count++;
|
||
stats->secret_read++;
|
||
stats->secret_dups++;
|
||
}
|
||
|
||
/* If we did not get an error from storing the secret key we return
|
||
a possible error from parsing the certificates. We do this after
|
||
storing the secret keys so that a bad certificate does not
|
||
inhibit our chance to store the secret key. */
|
||
if (!err && store_cert_parm.err)
|
||
err = store_cert_parm.err;
|
||
|
||
leave:
|
||
if (kparms)
|
||
{
|
||
for (i=0; i < 8; i++)
|
||
gcry_mpi_release (kparms[i]);
|
||
gcry_free (kparms);
|
||
kparms = NULL;
|
||
}
|
||
xfree (key);
|
||
gcry_sexp_release (s_key);
|
||
xfree (passphrase);
|
||
gcry_cipher_close (cipherhd);
|
||
xfree (wrappedkey);
|
||
xfree (kek);
|
||
xfree (get_membuf (&p12mbuf, NULL));
|
||
xfree (p12buffer);
|
||
|
||
if (bad_pass)
|
||
{
|
||
/* We only write a plain error code and not direct
|
||
BAD_PASSPHRASE because the pkcs12 parser might issue this
|
||
message multiple times, BAD_PASSPHRASE in general requires a
|
||
keyID and parts of the import might actually succeed so that
|
||
IMPORT_PROBLEM is also not appropriate. */
|
||
gpgsm_status_with_err_code (ctrl, STATUS_ERROR,
|
||
"import.parsep12", GPG_ERR_BAD_PASSPHRASE);
|
||
}
|
||
|
||
return err;
|
||
}
|