diff --git a/sm/ChangeLog b/sm/ChangeLog index d2864c77b..c31e766f7 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,3 +1,10 @@ +2002-01-11 Werner Koch + + * call-dirmngr.c: New. + * certpath.c (gpgsm_validate_path): Check the CRL here. + * fingerprint.c (gpgsm_get_certid): New. + * gpgsm.c: New options --dirmngr-program and --disable-crl-checks. + 2002-01-10 Werner Koch * base64.c (gpgsm_create_writer): Allow to set the object name diff --git a/sm/Makefile.am b/sm/Makefile.am index edd956693..8bdbc011b 100644 --- a/sm/Makefile.am +++ b/sm/Makefile.am @@ -30,6 +30,7 @@ gpgsm_SOURCES = \ keydb.c keydb.h \ server.c \ call-agent.c \ + call-dirmngr.c \ fingerprint.c \ base64.c \ certlist.c \ diff --git a/sm/call-agent.c b/sm/call-agent.c index 47a4ee710..87b10248a 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -1,5 +1,5 @@ /* call-agent.c - divert operations to the agent - * Copyright (C) 2001 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -33,16 +33,6 @@ #include "../assuan/assuan.h" #include "i18n.h" -#ifdef _POSIX_OPEN_MAX -#define MAX_OPEN_FDS _POSIX_OPEN_MAX -#else -#define MAX_OPEN_FDS 20 -#endif - -#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)) - static ASSUAN_CONTEXT agent_ctx = NULL; diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c new file mode 100644 index 000000000..4e3de3629 --- /dev/null +++ b/sm/call-dirmngr.c @@ -0,0 +1,182 @@ +/* call-dirmngr.c - communication with the dromngr + * Copyright (C) 2002 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gpgsm.h" +#include "../assuan/assuan.h" +#include "i18n.h" + +static ASSUAN_CONTEXT dirmngr_ctx = NULL; + +struct cipher_parm_s { + ASSUAN_CONTEXT ctx; + const char *ciphertext; + size_t ciphertextlen; +}; + +struct genkey_parm_s { + ASSUAN_CONTEXT ctx; + const char *sexp; + size_t sexplen; +}; + + +struct membuf { + size_t len; + size_t size; + char *buf; + int out_of_core; +}; + + + +/* Try to connect to the agent via socket or fork it off and work by + pipes. Handle the server's initial greeting */ +static int +start_dirmngr (void) +{ + int rc; + char *infostr, *p; + + if (dirmngr_ctx) + return 0; /* fixme: We need a context for each thread or serialize + the access to the agent (which is suitable given that + the agent is not MT */ + + infostr = getenv ("DIRMNGR_INFO"); + if (!infostr) + { + const char *pgmname; + ASSUAN_CONTEXT ctx; + const char *argv[3]; + + log_info (_("no running dirmngr - starting one\n")); + + if (fflush (NULL)) + { + log_error ("error flushing pending output: %s\n", strerror (errno)); + return seterr (Write_Error); + } + + if (!opt.dirmngr_program || !*opt.dirmngr_program) + opt.dirmngr_program = "/usr/sbin/dirmngr"; + if ( !(pgmname = strrchr (opt.dirmngr_program, '/'))) + pgmname = opt.dirmngr_program; + else + pgmname++; + + argv[0] = pgmname; + argv[1] = "--server"; + argv[2] = NULL; + + /* connect to the agent and perform initial handshaking */ + rc = assuan_pipe_connect (&ctx, opt.dirmngr_program, (char**)argv, 0); + if (rc) + { + log_error ("can't connect to the dirmngr: %s\n", assuan_strerror (rc)); + return seterr (No_Dirmngr); + } + dirmngr_ctx = ctx; + } + else + { + infostr = xstrdup (infostr); + if ( !(p = strchr (infostr, ':')) || p == infostr + /* || (p-infostr)+1 >= sizeof client_addr.sun_path */) + { + log_error (_("malformed DIRMNGR_INFO environment variable\n")); + xfree (infostr); + return seterr (General_Error); + } + *p = 0; + log_error (_("socket based dirmngr communication not yet implemented\n")); + return seterr (Not_Implemented); + } + + log_debug ("connection to dirmngr established\n"); + return 0; +} + + + +/* Handle a SENDCERT inquiry. */ +static AssuanError +inq_certificate (void *opaque, const char *line) +{ + AssuanError rc; + + if (strncmp (line, "SENDCERT ", 9) || !line[9]) + { + log_error ("unsupported inquiry `%s'\n", line); + return ASSUAN_Inquire_Unknown; + } + + /* rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen);*/ + rc = 0; + return rc; +} + + + +/* Call the directory manager to check whether the certificate is valid + Returns 0 for valid or usually one of the errors: + + GNUPG_Certificate_Revoked + GNUPG_No_CRL_Known + GNUPG_CRL_Too_Old + */ +int +gpgsm_dirmngr_isvalid (KsbaCert cert) +{ + int rc; + char *certid; + char line[ASSUAN_LINELENGTH]; + + rc = start_dirmngr (); + if (rc) + return rc; + + certid = gpgsm_get_certid (cert); + if (!certid) + { + log_error ("error getting the certificate ID\n"); + return seterr (General_Error); + } + + snprintf (line, DIM(line)-1, "ISVALID %s", certid); + line[DIM(line)-1] = 0; + xfree (certid); + + rc = assuan_transact (dirmngr_ctx, line, NULL, NULL, inq_certificate, NULL); + return map_assuan_err (rc); +} + + + diff --git a/sm/certchain.c b/sm/certchain.c index 31ddcd8fa..69a9c55fb 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -69,6 +69,33 @@ gpgsm_validate_path (KsbaCert cert) goto leave; } + if (!opt.no_crl_check) + { + rc = gpgsm_dirmngr_isvalid (subject_cert); + if (rc) + { + switch (rc) + { + case GNUPG_Certificate_Revoked: + log_error (_("the certificate has been revoked\n")); + break; + case GNUPG_No_CRL_Known: + log_error (_("no CRL found for certificate\n")); + break; + case GNUPG_CRL_Too_Old: + log_error (_("the available CRL is too old\n")); + log_info (_("please make sure that the " + "\"dirmngr\" is properly installed\n")); + break; + default: + log_error (_("checking the CRL failed: %s\n"), + gnupg_strerror (rc)); + break; + } + goto leave; + } + } + if (subject && !strcmp (issuer, subject)) { if (gpgsm_check_cert_sig (subject_cert, subject_cert) ) @@ -118,6 +145,10 @@ gpgsm_validate_path (KsbaCert cert) subject_cert = issuer_cert; issuer_cert = NULL; } + + if (opt.no_crl_check) + log_info ("CRL was not checked due to --no-crl-cechk option\n"); + leave: xfree (issuer); diff --git a/sm/certpath.c b/sm/certpath.c index 31ddcd8fa..69a9c55fb 100644 --- a/sm/certpath.c +++ b/sm/certpath.c @@ -69,6 +69,33 @@ gpgsm_validate_path (KsbaCert cert) goto leave; } + if (!opt.no_crl_check) + { + rc = gpgsm_dirmngr_isvalid (subject_cert); + if (rc) + { + switch (rc) + { + case GNUPG_Certificate_Revoked: + log_error (_("the certificate has been revoked\n")); + break; + case GNUPG_No_CRL_Known: + log_error (_("no CRL found for certificate\n")); + break; + case GNUPG_CRL_Too_Old: + log_error (_("the available CRL is too old\n")); + log_info (_("please make sure that the " + "\"dirmngr\" is properly installed\n")); + break; + default: + log_error (_("checking the CRL failed: %s\n"), + gnupg_strerror (rc)); + break; + } + goto leave; + } + } + if (subject && !strcmp (issuer, subject)) { if (gpgsm_check_cert_sig (subject_cert, subject_cert) ) @@ -118,6 +145,10 @@ gpgsm_validate_path (KsbaCert cert) subject_cert = issuer_cert; issuer_cert = NULL; } + + if (opt.no_crl_check) + log_info ("CRL was not checked due to --no-crl-cechk option\n"); + leave: xfree (issuer); diff --git a/sm/fingerprint.c b/sm/fingerprint.c index a8edf6f5a..fec5f14d5 100644 --- a/sm/fingerprint.c +++ b/sm/fingerprint.c @@ -187,6 +187,73 @@ gpgsm_get_keygrip_hexstring (KsbaCert cert) } + +/* For certain purposes we need a certificate id which has an upper + limit of the size. We use the hash of the issuer name and the + serial number for this. In most cases the serial number is not + that large and the resulting string can be passed on an assuan + command line. Everything is hexencoded with the serialnumber + delimted from the has by a dot. + + The caller must free the string. +*/ +char * +gpgsm_get_certid (KsbaCert cert) +{ + KsbaSexp serial; + unsigned char *p; + char *endp; + unsigned char hash[20]; + unsigned long n; + char *certid; + int i; + + p = ksba_cert_get_issuer (cert, 0); + if (!p) + return NULL; /* Ooops: No issuer */ + gcry_md_hash_buffer (GCRY_MD_SHA1, hash, p, strlen (p)); + xfree (p); + + serial = ksba_cert_get_serial (cert); + if (!serial) + return NULL; /* oops: no serial number */ + p = serial; + if (*p != '(') + { + log_error ("Ooops: invalid serial number\n"); + xfree (serial); + return NULL; + } + p++; + n = strtoul (p, &endp, 10); + p = endp; + if (*p != ':') + { + log_error ("Ooops: invalid serial number (no colon)\n"); + xfree (serial); + return NULL; + } + p++; + + certid = xtrymalloc ( 40 + 1 + n*2 + 1); + if (!certid) + { + xfree (serial); + return NULL; /* out of core */ + } + + for (i=0, endp = certid; i < 20; i++, endp += 2 ) + sprintf (endp, "%02X", hash[i]); + *endp++ = '.'; + for (i=0; i < n; i++, endp += 2) + sprintf (endp, "%02X", hash[i]); + *endp = 0; + + xfree (serial); + return certid; +} + + diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 130697f6b..0ca22b639 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -83,7 +83,7 @@ enum cmd_and_opt_values { oEnableSpecialFilenames, oAgentProgram, - + oDirmngrProgram, @@ -95,7 +95,7 @@ enum cmd_and_opt_values { oBase64, oNoArmor, - + oDisableCRLChecks, oTextmode, oFingerprint, @@ -224,6 +224,10 @@ static ARGPARSE_OPTS opts[] = { { oRecipient, "recipient", 2, N_("|NAME|encrypt for NAME")}, + + { oDisableCRLChecks, "disable-crl-checks", 0, N_("never consult a CRL")}, + + #if 0 { oDefRecipient, "default-recipient" ,2, N_("|NAME|use NAME as default recipient")}, @@ -315,6 +319,7 @@ static ARGPARSE_OPTS opts[] = { { oNoOptions, "no-options", 0, "@" }, /* shortcut for --options /dev/null */ { oHomedir, "homedir", 2, "@" }, /* defaults to "~/.gnupg" */ { oAgentProgram, "agent-program", 2 , "@" }, + { oDirmngrProgram, "dirmngr-program", 2 , "@" }, { oNoBatch, "no-batch", 0, "@" }, { oWithColons, "with-colons", 0, "@"}, @@ -727,6 +732,10 @@ main ( int argc, char **argv) ctrl.is_pem = 0; ctrl.is_base64 = 0; break; + + case oDisableCRLChecks: + opt.no_crl_check = 1; + break; case oOutput: opt.outfile = pargs.r.ret_str; break; @@ -780,6 +789,7 @@ main ( int argc, char **argv) case oNoOptions: break; /* no-options */ case oHomedir: opt.homedir = pargs.r.ret_str; break; case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; + case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; case oNoDefKeyring: default_keyring = 0; break; case oNoGreeting: nogreeting = 1; break; diff --git a/sm/gpgsm.h b/sm/gpgsm.h index bc2b5ec44..0ec923d76 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -39,6 +39,7 @@ struct { const char *homedir; /* configuration directory name */ const char *agent_program; + const char *dirmngr_program; char *outfile; /* name of output file */ int with_key_data;/* include raw key in the column delimted output */ @@ -65,6 +66,8 @@ struct { int ignore_time_conflict; /* Ignore certain time conflicts */ + int no_crl_check; /* Don't do a CRL check */ + } opt; @@ -126,6 +129,8 @@ char *gpgsm_get_fingerprint_string (KsbaCert cert, int algo); char *gpgsm_get_fingerprint_hexstring (KsbaCert cert, int algo); char *gpgsm_get_keygrip (KsbaCert cert, char *array); char *gpgsm_get_keygrip_hexstring (KsbaCert cert); +char *gpgsm_get_certid (KsbaCert cert); + /*-- base64.c --*/ int gpgsm_create_reader (Base64Context *ctx, @@ -190,6 +195,12 @@ int gpgsm_agent_pkdecrypt (const char *keygrip, char **r_buf, size_t *r_buflen); int gpgsm_agent_genkey (KsbaConstSexp keyparms, KsbaSexp *r_pubkey); +/*-- call-dirmngr.c --*/ +int gpgsm_dirmngr_isvalid (KsbaCert cert); + + + + #endif /*GPGSM_H*/