diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c index 7267d623e..483b6f32d 100644 --- a/dirmngr/ocsp.c +++ b/dirmngr/ocsp.c @@ -1,950 +1,977 @@ /* ocsp.c - OCSP management * Copyright (C) 2004, 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include "dirmngr.h" #include "misc.h" #include "http.h" #include "validate.h" #include "certcache.h" #include "ocsp.h" /* The maximum size we allow as a response from an OCSP reponder. */ #define MAX_RESPONSE_SIZE 65536 static const char oidstr_ocsp[] = "1.3.6.1.5.5.7.48.1"; /* Telesec attribute used to implement a positive confirmation. CertHash ::= SEQUENCE { HashAlgorithm AlgorithmIdentifier, certificateHash OCTET STRING } */ /* static const char oidstr_certHash[] = "1.3.36.8.3.13"; */ /* Read from FP and return a newly allocated buffer in R_BUFFER with the entire data read from FP. */ static gpg_error_t read_response (estream_t fp, unsigned char **r_buffer, size_t *r_buflen) { gpg_error_t err; unsigned char *buffer; size_t bufsize, nbytes; *r_buffer = NULL; *r_buflen = 0; bufsize = 4096; buffer = xtrymalloc (bufsize); if (!buffer) return gpg_error_from_errno (errno); nbytes = 0; for (;;) { unsigned char *tmp; size_t nread = 0; assert (nbytes < bufsize); nread = es_fread (buffer+nbytes, 1, bufsize-nbytes, fp); if (nread < bufsize-nbytes && es_ferror (fp)) { err = gpg_error_from_errno (errno); log_error (_("error reading from responder: %s\n"), strerror (errno)); xfree (buffer); return err; } if ( !(nread == bufsize-nbytes && !es_feof (fp))) { /* Response successfully received. */ nbytes += nread; *r_buffer = buffer; *r_buflen = nbytes; return 0; } nbytes += nread; /* Need to enlarge the buffer. */ if (bufsize >= MAX_RESPONSE_SIZE) { log_error (_("response from server too large; limit is %d bytes\n"), MAX_RESPONSE_SIZE); xfree (buffer); return gpg_error (GPG_ERR_TOO_LARGE); } bufsize += 4096; tmp = xtryrealloc (buffer, bufsize); if (!tmp) { err = gpg_error_from_errno (errno); xfree (buffer); return err; } buffer = tmp; } } /* Construct an OCSP request, send it to the configured OCSP responder and parse the response. On success the OCSP context may be used to further process the response. The signature value and the production date are returned at R_SIGVAL and R_PRODUCED_AT; they may be NULL or an empty string if not available. A new hash context is returned at R_MD. */ static gpg_error_t do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp, const char *url, ksba_cert_t cert, ksba_cert_t issuer_cert, ksba_sexp_t *r_sigval, ksba_isotime_t r_produced_at, gcry_md_hd_t *r_md) { gpg_error_t err; unsigned char *request, *response; size_t requestlen, responselen; http_t http; ksba_ocsp_response_status_t response_status; const char *t; int redirects_left = 2; char *free_this = NULL; (void)ctrl; *r_sigval = NULL; *r_produced_at = 0; *r_md = NULL; if (dirmngr_use_tor ()) { /* For now we do not allow OCSP via Tor due to possible privacy concerns. Needs further research. */ const char *msg = _("OCSP request not possible due to Tor mode"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); log_error ("%s", msg); dirmngr_status_printf (ctrl, "NOTE", "no_ocsp_due_to_tor %u %s", err,msg); return err; } if (opt.disable_http) { log_error (_("OCSP request not possible due to disabled HTTP\n")); return gpg_error (GPG_ERR_NOT_SUPPORTED); } err = ksba_ocsp_add_target (ocsp, cert, issuer_cert); if (err) { log_error (_("error setting OCSP target: %s\n"), gpg_strerror (err)); return err; } { size_t n; unsigned char nonce[32]; n = ksba_ocsp_set_nonce (ocsp, NULL, 0); if (n > sizeof nonce) n = sizeof nonce; gcry_create_nonce (nonce, n); ksba_ocsp_set_nonce (ocsp, nonce, n); } err = ksba_ocsp_build_request (ocsp, &request, &requestlen); if (err) { log_error (_("error building OCSP request: %s\n"), gpg_strerror (err)); return err; } once_more: err = http_open (ctrl, &http, HTTP_REQ_POST, url, NULL, NULL, ((opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0) | (dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0) | (opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0) | (opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)), ctrl->http_proxy, NULL, NULL, NULL); if (err) { log_error (_("error connecting to '%s': %s\n"), url, gpg_strerror (err)); xfree (free_this); return err; } es_fprintf (http_get_write_ptr (http), "Content-Type: application/ocsp-request\r\n" "Content-Length: %lu\r\n", (unsigned long)requestlen ); http_start_data (http); if (es_fwrite (request, requestlen, 1, http_get_write_ptr (http)) != 1) { err = gpg_error_from_errno (errno); log_error ("error sending request to '%s': %s\n", url, strerror (errno)); http_close (http, 0); xfree (request); xfree (free_this); return err; } xfree (request); request = NULL; err = http_wait_response (http); if (err || http_get_status_code (http) != 200) { if (err) log_error (_("error reading HTTP response for '%s': %s\n"), url, gpg_strerror (err)); else { switch (http_get_status_code (http)) { case 301: case 302: { const char *s = http_get_header (http, "Location"); log_info (_("URL '%s' redirected to '%s' (%u)\n"), url, s?s:"[none]", http_get_status_code (http)); if (s && *s && redirects_left-- ) { xfree (free_this); url = NULL; free_this = xtrystrdup (s); if (!free_this) err = gpg_error_from_errno (errno); else { url = free_this; http_close (http, 0); goto once_more; } } else err = gpg_error (GPG_ERR_NO_DATA); log_error (_("too many redirections\n")); } break; case 413: /* Payload too large */ err = gpg_error (GPG_ERR_TOO_LARGE); break; default: log_error (_("error accessing '%s': http status %u\n"), url, http_get_status_code (http)); err = gpg_error (GPG_ERR_NO_DATA); break; } } http_close (http, 0); xfree (free_this); return err; } err = read_response (http_get_read_ptr (http), &response, &responselen); http_close (http, 0); if (err) { log_error (_("error reading HTTP response for '%s': %s\n"), url, gpg_strerror (err)); xfree (free_this); return err; } /* log_printhex (response, responselen, "ocsp response"); */ err = ksba_ocsp_parse_response (ocsp, response, responselen, &response_status); if (err) { log_error (_("error parsing OCSP response for '%s': %s\n"), url, gpg_strerror (err)); xfree (response); xfree (free_this); return err; } switch (response_status) { case KSBA_OCSP_RSPSTATUS_SUCCESS: t = "success"; break; case KSBA_OCSP_RSPSTATUS_MALFORMED: t = "malformed"; break; case KSBA_OCSP_RSPSTATUS_INTERNAL: t = "internal error"; break; case KSBA_OCSP_RSPSTATUS_TRYLATER: t = "try later"; break; case KSBA_OCSP_RSPSTATUS_SIGREQUIRED: t = "must sign request"; break; case KSBA_OCSP_RSPSTATUS_UNAUTHORIZED: t = "unauthorized"; break; case KSBA_OCSP_RSPSTATUS_REPLAYED: t = "replay detected"; break; case KSBA_OCSP_RSPSTATUS_OTHER: t = "other (unknown)"; break; case KSBA_OCSP_RSPSTATUS_NONE: t = "no status"; break; default: t = "[unknown status]"; break; } if (response_status == KSBA_OCSP_RSPSTATUS_SUCCESS) { int hash_algo; if (opt.verbose) log_info (_("OCSP responder at '%s' status: %s\n"), url, t); /* Get the signature value now because we can call this function * only once. */ *r_sigval = ksba_ocsp_get_sig_val (ocsp, r_produced_at); hash_algo = hash_algo_from_sigval (*r_sigval); if (!hash_algo) { if (opt.verbose) log_info ("ocsp: using SHA-256 as fallback hash algo.\n"); hash_algo = GCRY_MD_SHA256; } err = gcry_md_open (r_md, hash_algo, 0); if (err) { log_error (_("failed to establish a hashing context for OCSP: %s\n"), gpg_strerror (err)); goto leave; } if (DBG_HASHING) gcry_md_debug (*r_md, "ocsp"); err = ksba_ocsp_hash_response (ocsp, response, responselen, HASH_FNC, *r_md); if (err) log_error (_("hashing the OCSP response for '%s' failed: %s\n"), url, gpg_strerror (err)); } else { log_error (_("OCSP responder at '%s' status: %s\n"), url, t); err = gpg_error (GPG_ERR_GENERAL); } leave: xfree (response); xfree (free_this); if (err) { xfree (*r_sigval); *r_sigval = NULL; *r_produced_at = 0; gcry_md_close (*r_md); *r_md = NULL; } return err; } /* Validate that CERT is indeed valid to sign an OCSP response. If SIGNER_FPR_LIST is not NULL we simply check that CERT matches one of the fingerprints in this list. */ static gpg_error_t validate_responder_cert (ctrl_t ctrl, ksba_cert_t cert, fingerprint_list_t signer_fpr_list) { gpg_error_t err; char *fpr; if (signer_fpr_list) { fpr = get_fingerprint_hexstring (cert); for (; signer_fpr_list && strcmp (signer_fpr_list->hexfpr, fpr); signer_fpr_list = signer_fpr_list->next) ; if (signer_fpr_list) err = 0; else { log_error (_("not signed by a default OCSP signer's certificate")); err = gpg_error (GPG_ERR_BAD_CA_CERT); } xfree (fpr); } else { /* We avoid duplicating the entire certificate validation code from gpgsm here. Because we have no way calling back to the client and letting it compute the validity, we use the ugly hack of telling the client that the response will only be valid if the certificate given in this status message is valid. Note, that in theory we could simply ask the client via an inquire to validate a certificate but this might involve calling DirMngr again recursively - we can't do that as of now (neither DirMngr nor gpgsm have the ability for concurrent access to DirMngr. */ /* FIXME: We should cache this certificate locally, so that the next call to dirmngr won't need to look it up - if this works at all. */ fpr = get_fingerprint_hexstring (cert); dirmngr_status (ctrl, "ONLY_VALID_IF_CERT_VALID", fpr, NULL); xfree (fpr); err = 0; } return err; } /* Helper for check_signature. MD is the finalized hash context. */ static gpg_error_t check_signature_core (ctrl_t ctrl, ksba_cert_t cert, gcry_sexp_t s_sig, gcry_md_hd_t md, fingerprint_list_t signer_fpr_list) { gpg_error_t err; gcry_sexp_t s_pkey = NULL; gcry_sexp_t s_hash = NULL; const char *s; int mdalgo, mdlen; /* Get the public key as a gcrypt s-expression. */ { ksba_sexp_t pk = ksba_cert_get_public_key (cert); if (!pk) err = gpg_error (GPG_ERR_INV_OBJ); else { err = canon_sexp_to_gcry (pk, &s_pkey); xfree (pk); } if (err) goto leave; } mdalgo = gcry_md_get_algo (md); mdlen = gcry_md_get_algo_dlen (mdalgo); if (pk_algo_from_sexp (s_pkey) == GCRY_PK_ECC) { unsigned int qbits0, qbits; qbits0 = gcry_pk_get_nbits (s_pkey); qbits = qbits0 == 521? 512 : qbits0; if ((qbits%8)) { log_error ("ECDSA requires the hash length to be a" " multiple of 8 bits\n"); err = gpg_error (GPG_ERR_INTERNAL); goto leave; } /* Don't allow any Q smaller than 160 bits. */ if (qbits < 160) { log_error (_("%s key uses an unsafe (%u bit) hash\n"), "ECDSA", qbits0); err = gpg_error (GPG_ERR_INTERNAL); goto leave; } /* Check if we're too short. */ if (mdlen < qbits/8) { log_error (_("a %u bit hash is not valid for a %u bit %s key\n"), (unsigned int)mdlen*8, qbits0, "ECDSA"); if (mdlen < 20) { err = gpg_error (GPG_ERR_INTERNAL); goto leave; } } /* Truncate. */ if (mdlen > qbits/8) mdlen = qbits/8; err = gcry_sexp_build (&s_hash, NULL, "(data(flags raw)(value %b))", (int)mdlen, gcry_md_read (md, mdalgo)); } else if (mdalgo && (s = gcry_md_algo_name (mdalgo)) && strlen (s) < 16) { /* Assume RSA */ char hashalgostr[16+1]; int i; for (i=0; s[i]; i++) hashalgostr[i] = ascii_tolower (s[i]); hashalgostr[i] = 0; err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))", hashalgostr, (int)mdlen, gcry_md_read (md, mdalgo)); } else err = gpg_error (GPG_ERR_DIGEST_ALGO); if (err) { log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err)); goto leave; } if (DBG_CRYPTO) { gcry_log_debugsxp ("sig ", s_sig); gcry_log_debugsxp ("hash", s_hash); } err = gcry_pk_verify (s_sig, s_hash, s_pkey); if (err) goto leave; err = validate_responder_cert (ctrl, cert, signer_fpr_list); leave: gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); return err; } /* Check the signature of an OCSP response. OCSP is the context, S_SIG the signature value and MD the handle of the hash we used for the response. This function automagically finds the correct public key. If SIGNER_FPR_LIST is not NULL, the default OCSP reponder has been used and thus the certificate is one of those identified by the fingerprints. */ static gpg_error_t check_signature (ctrl_t ctrl, ksba_ocsp_t ocsp, gcry_sexp_t s_sig, gcry_md_hd_t md, fingerprint_list_t signer_fpr_list) { gpg_error_t err; int cert_idx; ksba_cert_t cert; /* Create a suitable S-expression with the hash value of our response. */ gcry_md_final (md); /* Get rid of old OCSP specific certificate references. */ release_ctrl_ocsp_certs (ctrl); if (signer_fpr_list && !signer_fpr_list->next) { /* There is exactly one signer fingerprint given. Thus we use the default OCSP responder's certificate and instantly know the certificate to use. */ cert = get_cert_byhexfpr (signer_fpr_list->hexfpr); if (!cert) cert = get_cert_local (ctrl, signer_fpr_list->hexfpr); if (cert) { err = check_signature_core (ctrl, cert, s_sig, md, signer_fpr_list); ksba_cert_release (cert); cert = NULL; if (!err) { return 0; /* Successfully verified the signature. */ } } } else { char *name; ksba_sexp_t keyid; /* Put all certificates included in the response into the cache and setup a list of those certificate which will later be preferred used when locating certificates. */ for (cert_idx=0; (cert = ksba_ocsp_get_cert (ocsp, cert_idx)); cert_idx++) { cert_ref_t cref; /* dump_cert ("from ocsp response", cert); */ cref = xtrymalloc (sizeof *cref); if (!cref) { err = gpg_error_from_syserror (); log_error (_("allocating list item failed: %s\n"), gpg_strerror (err)); } else if (!cache_cert_silent (cert, &cref->fpr)) { cref->next = ctrl->ocsp_certs; ctrl->ocsp_certs = cref; } else xfree (cref); } /* Get the certificate by means of the responder ID. */ err = ksba_ocsp_get_responder_id (ocsp, &name, &keyid); if (err) { log_error (_("error getting responder ID: %s\n"), gcry_strerror (err)); return err; } cert = find_cert_bysubject (ctrl, name, keyid); if (!cert) { log_error ("responder certificate "); if (name) log_printf ("'/%s' ", name); if (keyid) { log_printf ("{"); dump_serial (keyid); log_printf ("} "); } log_printf ("not found\n"); } if (cert) { err = check_signature_core (ctrl, cert, s_sig, md, signer_fpr_list); ksba_cert_release (cert); if (!err) { ksba_free (name); ksba_free (keyid); return 0; /* Successfully verified the signature. */ } log_error ("responder certificate "); if (name) log_printf ("'/%s' ", name); if (keyid) { log_printf ("{"); dump_serial (keyid); log_printf ("} "); } log_printf ("did not verify: %s\n", gpg_strerror (err)); } ksba_free (name); ksba_free (keyid); } log_error (_("no suitable certificate found to verify the OCSP response\n")); return gpg_error (GPG_ERR_NO_PUBKEY); } /* Check whether the certificate either given by fingerprint CERT_FPR or directly through the CERT object is valid by running an OCSP transaction. With FORCE_DEFAULT_RESPONDER set only the configured - default responder is used. */ + default responder is used. If R_REVOKED_AT or R_REASON are not + NULL and the certificat has been revoked the revocation time and + the reasons are stored there. */ gpg_error_t ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, - int force_default_responder) + int force_default_responder, ksba_isotime_t r_revoked_at, + const char **r_reason) { gpg_error_t err; ksba_ocsp_t ocsp = NULL; ksba_cert_t issuer_cert = NULL; ksba_sexp_t sigval = NULL; gcry_sexp_t s_sig = NULL; ksba_isotime_t current_time; ksba_isotime_t this_update, next_update, revocation_time, produced_at; ksba_isotime_t tmp_time; ksba_status_t status; ksba_crl_reason_t reason; char *url_buffer = NULL; const char *url; gcry_md_hd_t md = NULL; int i, idx; char *oid; ksba_name_t name; fingerprint_list_t default_signer = NULL; + const char *sreason; + + if (r_revoked_at) + *r_revoked_at = 0; + if (r_reason) + *r_reason = NULL; /* Get the certificate. */ if (cert) { ksba_cert_ref (cert); err = find_issuing_cert (ctrl, cert, &issuer_cert); if (err) { log_error (_("issuer certificate not found: %s\n"), gpg_strerror (err)); goto leave; } } else { cert = get_cert_local (ctrl, cert_fpr); if (!cert) { log_error (_("caller did not return the target certificate\n")); err = gpg_error (GPG_ERR_GENERAL); goto leave; } issuer_cert = get_issuing_cert_local (ctrl, NULL); if (!issuer_cert) { log_error (_("caller did not return the issuing certificate\n")); err = gpg_error (GPG_ERR_GENERAL); goto leave; } } /* Create an OCSP instance. */ err = ksba_ocsp_new (&ocsp); if (err) { log_error (_("failed to allocate OCSP context: %s\n"), gpg_strerror (err)); goto leave; } /* Figure out the OCSP responder to use. 1. Try to get the reponder from the certificate. We do only take http and https style URIs into account. 2. If this fails use the default responder, if any. */ url = NULL; for (idx=0; !url && !opt.ignore_ocsp_service_url && !force_default_responder && !(err=ksba_cert_get_authority_info_access (cert, idx, &oid, &name)); idx++) { if ( !strcmp (oid, oidstr_ocsp) ) { for (i=0; !url && ksba_name_enum (name, i); i++) { char *p = ksba_name_get_uri (name, i); if (p && (!ascii_strncasecmp (p, "http:", 5) || !ascii_strncasecmp (p, "https:", 6))) url = url_buffer = p; else xfree (p); } } ksba_name_release (name); ksba_free (oid); } if (err && gpg_err_code (err) != GPG_ERR_EOF) { log_error (_("can't get authorityInfoAccess: %s\n"), gpg_strerror (err)); goto leave; } if (!url) { if (!opt.ocsp_responder || !*opt.ocsp_responder) { log_info (_("no default OCSP responder defined\n")); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } if (!opt.ocsp_signer) { log_info (_("no default OCSP signer defined\n")); err = gpg_error (GPG_ERR_CONFIGURATION); goto leave; } url = opt.ocsp_responder; default_signer = opt.ocsp_signer; if (opt.verbose) log_info (_("using default OCSP responder '%s'\n"), url); } else { if (opt.verbose) log_info (_("using OCSP responder '%s'\n"), url); } /* Ask the OCSP responder. */ err = do_ocsp_request (ctrl, ocsp, url, cert, issuer_cert, &sigval, produced_at, &md); if (err) goto leave; /* It is sometimes useful to know the responder ID. */ if (opt.verbose) { char *resp_name; ksba_sexp_t resp_keyid; err = ksba_ocsp_get_responder_id (ocsp, &resp_name, &resp_keyid); if (err) log_info (_("error getting responder ID: %s\n"), gpg_strerror (err)); else { log_info ("responder id: "); if (resp_name) log_printf ("'/%s' ", resp_name); if (resp_keyid) { log_printf ("{"); dump_serial (resp_keyid); log_printf ("} "); } log_printf ("\n"); } ksba_free (resp_name); ksba_free (resp_keyid); err = 0; } /* We got a useful answer, check that the answer has a valid signature. */ if (!sigval || !*produced_at || !md) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } if ( (err = canon_sexp_to_gcry (sigval, &s_sig)) ) goto leave; xfree (sigval); sigval = NULL; err = check_signature (ctrl, ocsp, s_sig, md, default_signer); if (err) goto leave; /* We only support one certificate per request. Check that the answer matches the right certificate. */ err = ksba_ocsp_get_status (ocsp, cert, &status, this_update, next_update, revocation_time, &reason); if (err) { log_error (_("error getting OCSP status for target certificate: %s\n"), gpg_strerror (err)); goto leave; } /* In case the certificate has been revoked, we better invalidate our cached validation status. */ if (status == KSBA_STATUS_REVOKED) { time_t validated_at = 0; /* That is: No cached validation available. */ err = ksba_cert_set_user_data (cert, "validated_at", &validated_at, sizeof (validated_at)); if (err) { log_error ("set_user_data(validated_at) failed: %s\n", gpg_strerror (err)); err = 0; /* The certificate is anyway revoked, and that is a more important message than the failure of our cache. */ } - } + switch (reason) + { + case KSBA_CRLREASON_UNSPECIFIED: + sreason = "unspecified"; break; + case KSBA_CRLREASON_KEY_COMPROMISE: + sreason = "key compromise"; break; + case KSBA_CRLREASON_CA_COMPROMISE: + sreason = "CA compromise"; break; + case KSBA_CRLREASON_AFFILIATION_CHANGED: + sreason = "affiliation changed"; break; + case KSBA_CRLREASON_SUPERSEDED: + sreason = "superseded"; break; + case KSBA_CRLREASON_CESSATION_OF_OPERATION: + sreason = "cessation of operation"; break; + case KSBA_CRLREASON_CERTIFICATE_HOLD: + sreason = "certificate on hold"; break; + case KSBA_CRLREASON_REMOVE_FROM_CRL: + sreason = "removed from CRL"; break; + case KSBA_CRLREASON_PRIVILEGE_WITHDRAWN: + sreason = "privilege withdrawn"; break; + case KSBA_CRLREASON_AA_COMPROMISE: + sreason = "AA compromise"; break; + case KSBA_CRLREASON_OTHER: + sreason = "other"; break; + default: sreason = "?"; break; + } + } + else + sreason = ""; if (opt.verbose) { log_info (_("certificate status is: %s (this=%s next=%s)\n"), status == KSBA_STATUS_GOOD? _("good"): status == KSBA_STATUS_REVOKED? _("revoked"): status == KSBA_STATUS_UNKNOWN? _("unknown"): status == KSBA_STATUS_NONE? _("none"): "?", this_update, next_update); if (status == KSBA_STATUS_REVOKED) log_info (_("certificate has been revoked at: %s due to: %s\n"), - revocation_time, - reason == KSBA_CRLREASON_UNSPECIFIED? "unspecified": - reason == KSBA_CRLREASON_KEY_COMPROMISE? "key compromise": - reason == KSBA_CRLREASON_CA_COMPROMISE? "CA compromise": - reason == KSBA_CRLREASON_AFFILIATION_CHANGED? - "affiliation changed": - reason == KSBA_CRLREASON_SUPERSEDED? "superseded": - reason == KSBA_CRLREASON_CESSATION_OF_OPERATION? - "cessation of operation": - reason == KSBA_CRLREASON_CERTIFICATE_HOLD? - "certificate on hold": - reason == KSBA_CRLREASON_REMOVE_FROM_CRL? - "removed from CRL": - reason == KSBA_CRLREASON_PRIVILEGE_WITHDRAWN? - "privilege withdrawn": - reason == KSBA_CRLREASON_AA_COMPROMISE? "AA compromise": - reason == KSBA_CRLREASON_OTHER? "other":"?"); + revocation_time, sreason); } if (status == KSBA_STATUS_REVOKED) - err = gpg_error (GPG_ERR_CERT_REVOKED); + { + err = gpg_error (GPG_ERR_CERT_REVOKED); + if (r_revoked_at) + gnupg_copy_time (r_revoked_at, revocation_time); + if (r_reason) + *r_reason = sreason; + } else if (status == KSBA_STATUS_UNKNOWN) err = gpg_error (GPG_ERR_NO_DATA); else if (status != KSBA_STATUS_GOOD) err = gpg_error (GPG_ERR_GENERAL); /* Allow for some clock skew. */ gnupg_get_isotime (current_time); add_seconds_to_isotime (current_time, opt.ocsp_max_clock_skew); if (strcmp (this_update, current_time) > 0 ) { log_error (_("OCSP responder returned a status in the future\n")); log_info ("used now: %s this_update: %s\n", current_time, this_update); if (!err) err = gpg_error (GPG_ERR_TIME_CONFLICT); } /* Check that THIS_UPDATE is not too far back in the past. */ gnupg_copy_time (tmp_time, this_update); add_seconds_to_isotime (tmp_time, opt.ocsp_max_period+opt.ocsp_max_clock_skew); if (!*tmp_time || strcmp (tmp_time, current_time) < 0 ) { log_error (_("OCSP responder returned a non-current status\n")); log_info ("used now: %s this_update: %s\n", current_time, this_update); if (!err) err = gpg_error (GPG_ERR_TIME_CONFLICT); } /* Check that we are not beyond NEXT_UPDATE (plus some extra time). */ if (*next_update) { gnupg_copy_time (tmp_time, next_update); add_seconds_to_isotime (tmp_time, opt.ocsp_current_period+opt.ocsp_max_clock_skew); if (!*tmp_time && strcmp (tmp_time, current_time) < 0 ) { log_error (_("OCSP responder returned an too old status\n")); log_info ("used now: %s next_update: %s\n", current_time, next_update); if (!err) err = gpg_error (GPG_ERR_TIME_CONFLICT); } } leave: gcry_md_close (md); gcry_sexp_release (s_sig); xfree (sigval); ksba_cert_release (issuer_cert); ksba_cert_release (cert); ksba_ocsp_release (ocsp); xfree (url_buffer); return err; } /* Release the list of OCSP certificates hold in the CTRL object. */ void release_ctrl_ocsp_certs (ctrl_t ctrl) { while (ctrl->ocsp_certs) { cert_ref_t tmp = ctrl->ocsp_certs->next; xfree (ctrl->ocsp_certs); ctrl->ocsp_certs = tmp; } } diff --git a/dirmngr/ocsp.h b/dirmngr/ocsp.h index cfab7dd6f..b3deeac93 100644 --- a/dirmngr/ocsp.h +++ b/dirmngr/ocsp.h @@ -1,31 +1,33 @@ /* ocsp.h - OCSP management * Copyright (C) 2003 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ #ifndef OCSP_H #define OCSP_H gpg_error_t ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, - int force_default_responder); + int force_default_responder, + gnupg_isotime_t r_revoked_at, + const char **r_reason); /* Release the list of OCSP certificates hold in the CTRL object. */ void release_ctrl_ocsp_certs (ctrl_t ctrl); #endif /*OCSP_H*/ diff --git a/dirmngr/server.c b/dirmngr/server.c index 98f354300..fba2233d4 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -1,3273 +1,3289 @@ /* server.c - LDAP and Keyserver access server * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009, 2011, 2015 g10 Code GmbH * Copyright (C) 2014, 2015, 2016 Werner Koch * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik * * 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 . * * SPDX-License-Identifier: GPL-3.0+ */ #include #include #include #include #include #include #include #include #include #include #include "dirmngr.h" #include #include "crlcache.h" #include "crlfetch.h" #if USE_LDAP # include "ldapserver.h" #endif #include "ocsp.h" #include "certcache.h" #include "validate.h" #include "misc.h" #if USE_LDAP # include "ldap-wrapper.h" #endif #include "ks-action.h" #include "ks-engine.h" #if USE_LDAP # include "ldap-parse-uri.h" #endif #include "dns-stuff.h" #include "../common/mbox-util.h" #include "../common/zb32.h" #include "../common/server-help.h" /* To avoid DoS attacks we limit the size of a certificate to something reasonable. The DoS was actually only an issue back when Dirmngr was a system service and not a user service. */ #define MAX_CERT_LENGTH (16*1024) /* The limit for the CERTLIST inquiry. We allow for up to 20 * certificates but also take PEM encoding into account. */ #define MAX_CERTLIST_LENGTH ((MAX_CERT_LENGTH * 20 * 4)/3) /* The same goes for OpenPGP keyblocks, but here we need to allow for much longer blocks; a 200k keyblock is not too unusual for keys with a lot of signatures (e.g. 0x5b0358a2). 9C31503C6D866396 even has 770 KiB as of 2015-08-23. To avoid adding a runtime option we now use 20MiB which should really be enough. Well, a key with several pictures could be larger (the parser as a 18MiB limit for attribute packets) but it won't be nice to the keyservers to send them such large blobs. */ #define MAX_KEYBLOCK_LENGTH (20*1024*1024) #define PARM_ERROR(t) assuan_set_error (ctx, \ gpg_error (GPG_ERR_ASS_PARAMETER), (t)) #define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \ /**/: gpg_error (e)) /* Control structure per connection. */ struct server_local_s { /* Data used to associate an Assuan context with local server data */ assuan_context_t assuan_ctx; /* The session id (a counter). */ unsigned int session_id; /* Per-session LDAP servers. */ ldap_server_t ldapservers; /* Per-session list of keyservers. */ uri_item_t keyservers; /* If this flag is set to true this dirmngr process will be terminated after the end of this session. */ int stopme; /* State variable private to is_tor_running. */ int tor_state; /* If the first both flags are set the assuan logging of data lines * is suppressed. The count variable is used to show the number of * non-logged bytes. */ size_t inhibit_data_logging_count; unsigned int inhibit_data_logging : 1; unsigned int inhibit_data_logging_now : 1; }; /* Cookie definition for assuan data line output. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size); static int data_line_cookie_close (void *cookie); static es_cookie_io_functions_t data_line_cookie_functions = { NULL, data_line_cookie_write, NULL, data_line_cookie_close }; /* Local prototypes */ static const char *task_check_wkd_support (ctrl_t ctrl, const char *domain); /* Accessor for the local ldapservers variable. */ ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl) { if (ctrl && ctrl->server_local) return ctrl->server_local->ldapservers; else return NULL; } /* Release an uri_item_t list. */ static void release_uri_item_list (uri_item_t list) { while (list) { uri_item_t tmp = list->next; http_release_parsed_uri (list->parsed_uri); xfree (list); list = tmp; } } /* Release all configured keyserver info from CTRL. */ void release_ctrl_keyservers (ctrl_t ctrl) { if (! ctrl->server_local) return; release_uri_item_list (ctrl->server_local->keyservers); ctrl->server_local->keyservers = NULL; } /* Helper to print a message while leaving a command. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } /* This is a wrapper around assuan_send_data which makes debugging the output in verbose mode easier. */ static gpg_error_t data_line_write (assuan_context_t ctx, const void *buffer_arg, size_t size) { ctrl_t ctrl = assuan_get_pointer (ctx); const char *buffer = buffer_arg; gpg_error_t err; /* If we do not want logging, enable it here. */ if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) ctrl->server_local->inhibit_data_logging_now = 1; if (opt.verbose && buffer && size) { /* Ease reading of output by sending a physical line at each LF. */ const char *p; size_t n, nbytes; nbytes = size; do { p = memchr (buffer, '\n', nbytes); n = p ? (p - buffer) + 1 : nbytes; err = assuan_send_data (ctx, buffer, n); if (err) { gpg_err_set_errno (EIO); goto leave; } buffer += n; nbytes -= n; if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */ { gpg_err_set_errno (EIO); goto leave; } } while (nbytes); } else { err = assuan_send_data (ctx, buffer, size); if (err) { gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */ goto leave; } } leave: if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) { ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count += size; } return err; } /* A write handler used by es_fopencookie to write assuan data lines. */ static gpgrt_ssize_t data_line_cookie_write (void *cookie, const void *buffer, size_t size) { assuan_context_t ctx = cookie; if (data_line_write (ctx, buffer, size)) return -1; return (gpgrt_ssize_t)size; } static int data_line_cookie_close (void *cookie) { assuan_context_t ctx = cookie; if (DBG_IPC) { ctrl_t ctrl = assuan_get_pointer (ctx); if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging && ctrl->server_local->inhibit_data_logging_count) log_debug ("(%zu bytes sent via D lines not shown)\n", ctrl->server_local->inhibit_data_logging_count); } if (assuan_send_data (ctx, NULL, 0)) { gpg_err_set_errno (EIO); return -1; } return 0; } /* Copy the % and + escaped string S into the buffer D and replace the escape sequences. Note, that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1. Note further that if S contains an escaped binary Nul the resulting string D will contain the 0 as well as all other characters but it will be impossible to know whether this is the original EOS or a copied Nul. */ static void strcpy_escaped_plus (char *d, const unsigned char *s) { while (*s) { if (*s == '%' && s[1] && s[2]) { s++; *d++ = xtoi_2 ( s); s += 2; } else if (*s == '+') *d++ = ' ', s++; else *d++ = *s++; } *d = 0; } /* This function returns true if a Tor server is running. The status * is cached for the current connection. */ static int is_tor_running (ctrl_t ctrl) { /* Check whether we can connect to the proxy. */ if (!ctrl || !ctrl->server_local) return 0; /* Ooops. */ if (!ctrl->server_local->tor_state) { assuan_fd_t sock; sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR); if (sock == ASSUAN_INVALID_FD) ctrl->server_local->tor_state = -1; /* Not running. */ else { assuan_sock_close (sock); ctrl->server_local->tor_state = 1; /* Running. */ } } return (ctrl->server_local->tor_state > 0); } /* Return an error if the assuan context does not belong to the owner of the process or to root. On error FAILTEXT is set as Assuan error string. */ static gpg_error_t check_owner_permission (assuan_context_t ctx, const char *failtext) { #ifdef HAVE_W32_SYSTEM /* Under Windows the dirmngr is always run under the control of the user. */ (void)ctx; (void)failtext; #else gpg_err_code_t ec; assuan_peercred_t cred; ec = gpg_err_code (assuan_get_peercred (ctx, &cred)); if (!ec && cred->uid && cred->uid != getuid ()) ec = GPG_ERR_EPERM; if (ec) return set_error (ec, failtext); #endif return 0; } /* Common code for get_cert_local and get_issuer_cert_local. */ static ksba_cert_t do_get_cert_local (ctrl_t ctrl, const char *name, const char *command) { unsigned char *value; size_t valuelen; int rc; char *buf; ksba_cert_t cert; buf = name? strconcat (command, " ", name, NULL) : xtrystrdup (command); if (!buf) rc = gpg_error_from_syserror (); else { rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf, &value, &valuelen, MAX_CERT_LENGTH); xfree (buf); } if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), command, gpg_strerror (rc)); return NULL; } if (!valuelen) { xfree (value); return NULL; } rc = ksba_cert_new (&cert); if (!rc) { rc = ksba_cert_init_from_mem (cert, value, valuelen); if (rc) { ksba_cert_release (cert); cert = NULL; } } xfree (value); return cert; } /* Ask back to return a certificate for NAME, given as a regular gpgsm * certificate identifier (e.g. fingerprint or one of the other * methods). Alternatively, NULL may be used for NAME to return the * current target certificate. Either return the certificate in a * KSBA object or NULL if it is not available. */ ksba_cert_t get_cert_local (ctrl_t ctrl, const char *name) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_cert_local called w/o context\n"); return NULL; } return do_get_cert_local (ctrl, name, "SENDCERT"); } /* Ask back to return the issuing certificate for NAME, given as a * regular gpgsm certificate identifier (e.g. fingerprint or one * of the other methods). Alternatively, NULL may be used for NAME to * return the current target certificate. Either return the certificate * in a KSBA object or NULL if it is not available. */ ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *name) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_issuing_cert_local called w/o context\n"); return NULL; } return do_get_cert_local (ctrl, name, "SENDISSUERCERT"); } /* Ask back to return a certificate with subject NAME and a * subjectKeyIdentifier of KEYID. */ ksba_cert_t get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid) { unsigned char *value; size_t valuelen; int rc; char *buf; ksba_cert_t cert; char *hexkeyid; if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) { if (opt.debug) log_debug ("get_cert_local_ski called w/o context\n"); return NULL; } if (!name || !keyid) { log_debug ("get_cert_local_ski called with insufficient arguments\n"); return NULL; } hexkeyid = serial_hex (keyid); if (!hexkeyid) { log_debug ("serial_hex() failed\n"); return NULL; } buf = strconcat ("SENDCERT_SKI ", hexkeyid, " /", name, NULL); if (!buf) { log_error ("can't allocate enough memory: %s\n", strerror (errno)); xfree (hexkeyid); return NULL; } xfree (hexkeyid); rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf, &value, &valuelen, MAX_CERT_LENGTH); xfree (buf); if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), "SENDCERT_SKI", gpg_strerror (rc)); return NULL; } if (!valuelen) { xfree (value); return NULL; } rc = ksba_cert_new (&cert); if (!rc) { rc = ksba_cert_init_from_mem (cert, value, valuelen); if (rc) { ksba_cert_release (cert); cert = NULL; } } xfree (value); return cert; } /* Ask the client via an inquiry to check the istrusted status of the certificate specified by the hexified fingerprint HEXFPR. Returns 0 if the certificate is trusted by the client or an error code. */ gpg_error_t get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr) { unsigned char *value; size_t valuelen; int rc; char request[100]; if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx || !hexfpr) return gpg_error (GPG_ERR_INV_ARG); snprintf (request, sizeof request, "ISTRUSTED %s", hexfpr); rc = assuan_inquire (ctrl->server_local->assuan_ctx, request, &value, &valuelen, 100); if (rc) { log_error (_("assuan_inquire(%s) failed: %s\n"), request, gpg_strerror (rc)); return rc; } /* The expected data is: "1" or "1 cruft" (not a C-string). */ if (valuelen && *value == '1' && (valuelen == 1 || spacep (value+1))) rc = 0; else rc = gpg_error (GPG_ERR_NOT_TRUSTED); xfree (value); return rc; } /* Ask the client to return the certificate associated with the current command. This is sometimes needed because the client usually sends us just the cert ID, assuming that the request can be satisfied from the cache, where the cert ID is used as key. */ static int inquire_cert_and_load_crl (assuan_context_t ctx) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char *value = NULL; size_t valuelen; ksba_cert_t cert = NULL; err = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0); if (err) return err; /* { */ /* FILE *fp = fopen ("foo.der", "r"); */ /* value = xmalloc (2000); */ /* valuelen = fread (value, 1, 2000, fp); */ /* fclose (fp); */ /* } */ if (!valuelen) /* No data returned; return a comprehensible error. */ return gpg_error (GPG_ERR_MISSING_CERT); err = ksba_cert_new (&cert); if (err) goto leave; err = ksba_cert_init_from_mem (cert, value, valuelen); if(err) goto leave; xfree (value); value = NULL; err = crl_cache_reload_crl (ctrl, cert); leave: ksba_cert_release (cert); xfree (value); return err; } /* Handle OPTION commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "force-crl-refresh")) { int i = *value? atoi (value) : 0; ctrl->force_crl_refresh = i; } else if (!strcmp (key, "audit-events")) { int i = *value? atoi (value) : 0; ctrl->audit_events = i; } else if (!strcmp (key, "http-proxy")) { xfree (ctrl->http_proxy); if (!*value || !strcmp (value, "none")) ctrl->http_proxy = NULL; else if (!(ctrl->http_proxy = xtrystrdup (value))) err = gpg_error_from_syserror (); } else if (!strcmp (key, "honor-keyserver-url-used")) { /* Return an error if we are running in Tor mode. */ if (dirmngr_use_tor ()) err = gpg_error (GPG_ERR_FORBIDDEN); } else if (!strcmp (key, "http-crl")) { int i = *value? atoi (value) : 0; ctrl->http_no_crl = !i; } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static const char hlp_dns_cert[] = "DNS_CERT \n" "DNS_CERT --pka \n" "DNS_CERT --dane \n" "\n" "Return the CERT record for . is one of\n" " * Return the first record of any supported subtype\n" " PGP Return the first record of subtype PGP (3)\n" " IPGP Return the first record of subtype IPGP (6)\n" "If the content of a certificate is available (PGP) it is returned\n" "by data lines. Fingerprints and URLs are returned via status lines.\n" "In --pka mode the fingerprint and if available an URL is returned.\n" "In --dane mode the key is returned from RR type 61"; static gpg_error_t cmd_dns_cert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int pka_mode, dane_mode; char *mbox = NULL; char *namebuf = NULL; char *encodedhash = NULL; const char *name; int certtype; char *p; void *key = NULL; size_t keylen; unsigned char *fpr = NULL; size_t fprlen; char *url = NULL; pka_mode = has_option (line, "--pka"); dane_mode = has_option (line, "--dane"); line = skip_options (line); if (pka_mode && dane_mode) { err = PARM_ERROR ("either --pka or --dane may be given"); goto leave; } if (pka_mode || dane_mode) ; /* No need to parse here - we do this later. */ else { p = strchr (line, ' '); if (!p) { err = PARM_ERROR ("missing arguments"); goto leave; } *p++ = 0; if (!strcmp (line, "*")) certtype = DNS_CERTTYPE_ANY; else if (!strcmp (line, "IPGP")) certtype = DNS_CERTTYPE_IPGP; else if (!strcmp (line, "PGP")) certtype = DNS_CERTTYPE_PGP; else { err = PARM_ERROR ("unknown subtype"); goto leave; } while (spacep (p)) p++; line = p; if (!*line) { err = PARM_ERROR ("name missing"); goto leave; } } if (pka_mode || dane_mode) { char *domain; /* Points to mbox. */ char hashbuf[32]; /* For SHA-1 and SHA-256. */ /* We lowercase ascii characters but the DANE I-D does not allow this. FIXME: Check after the release of the RFC whether to change this. */ mbox = mailbox_from_userid (line, 0); if (!mbox || !(domain = strchr (mbox, '@'))) { err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); goto leave; } *domain++ = 0; if (pka_mode) { gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox)); encodedhash = zb32_encode (hashbuf, 8*20); if (!encodedhash) { err = gpg_error_from_syserror (); goto leave; } namebuf = strconcat (encodedhash, "._pka.", domain, NULL); if (!namebuf) { err = gpg_error_from_syserror (); goto leave; } name = namebuf; certtype = DNS_CERTTYPE_IPGP; } else { /* Note: The hash is truncated to 28 bytes and we lowercase the result only for aesthetic reasons. */ gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox)); encodedhash = bin2hex (hashbuf, 28, NULL); if (!encodedhash) { err = gpg_error_from_syserror (); goto leave; } ascii_strlwr (encodedhash); namebuf = strconcat (encodedhash, "._openpgpkey.", domain, NULL); if (!namebuf) { err = gpg_error_from_syserror (); goto leave; } name = namebuf; certtype = DNS_CERTTYPE_RR61; } } else name = line; err = get_dns_cert (ctrl, name, certtype, &key, &keylen, &fpr, &fprlen, &url); if (err) goto leave; if (key) { err = data_line_write (ctx, key, keylen); if (err) goto leave; } if (fpr) { char *tmpstr; tmpstr = bin2hex (fpr, fprlen, NULL); if (!tmpstr) err = gpg_error_from_syserror (); else { err = assuan_write_status (ctx, "FPR", tmpstr); xfree (tmpstr); } if (err) goto leave; } if (url) { err = assuan_write_status (ctx, "URL", url); if (err) goto leave; } leave: xfree (key); xfree (fpr); xfree (url); xfree (mbox); xfree (namebuf); xfree (encodedhash); return leave_cmd (ctx, err); } /* Core of cmd_wkd_get and task_check_wkd_support. If CTX is NULL * this function will not write anything to the assuan output. */ static gpg_error_t proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) { gpg_error_t err = 0; char *mbox = NULL; char *domainbuf = NULL; char *domain; /* Points to mbox or domainbuf. This is used to * connect to the host. */ char *domain_orig;/* Points to mbox. This is the used for the * query; i.e. the domain part of the * addrspec. */ char sha1buf[20]; char *uri = NULL; char *encodedhash = NULL; int opt_submission_addr; int opt_policy_flags; int is_wkd_query; /* True if this is a real WKD query. */ int no_log = 0; char portstr[20] = { 0 }; int subdomain_mode = 0; opt_submission_addr = has_option (line, "--submission-address"); opt_policy_flags = has_option (line, "--policy-flags"); if (has_option (line, "--quick")) ctrl->timeout = opt.connect_quick_timeout; line = skip_options (line); is_wkd_query = !(opt_policy_flags || opt_submission_addr); mbox = mailbox_from_userid (line, 0); if (!mbox || !(domain = strchr (mbox, '@'))) { err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); goto leave; } *domain++ = 0; domain_orig = domain; /* Let's check whether we already know that the domain does not * support WKD. */ if (is_wkd_query) { if (domaininfo_is_wkd_not_supported (domain_orig)) { err = gpg_error (GPG_ERR_NO_DATA); dirmngr_status_printf (ctrl, "NOTE", "wkd_cached_result %u", err); goto leave; } } /* First try the new "openpgp" subdomain. We check that the domain * is valid because it is later used as an unescaped filename part * of the URI. */ if (is_valid_domain_name (domain_orig)) { dns_addrinfo_t aibuf; domainbuf = strconcat ( "openpgpkey.", domain_orig, NULL); if (!domainbuf) { err = gpg_error_from_syserror (); goto leave; } /* FIXME: We should put a cache into dns-stuff because the same * query (with a different port and socket type, though) will be * done later by http function. */ err = resolve_dns_name (ctrl, domainbuf, 0, 0, 0, &aibuf, NULL); if (err) { err = 0; xfree (domainbuf); domainbuf = NULL; } else /* Got a subdomain. */ { free_dns_addrinfo (aibuf); subdomain_mode = 1; domain = domainbuf; } } /* Check for SRV records unless we have a subdomain. */ if (!subdomain_mode) { struct srventry *srvs; unsigned int srvscount; size_t domainlen, targetlen; int i; err = get_dns_srv (ctrl, domain, "openpgpkey", NULL, &srvs, &srvscount); if (err) { /* Ignore server failed becuase there are too many resolvers * which do not work as expected. */ if (gpg_err_code (err) == GPG_ERR_SERVER_FAILED) err = 0; /*(srvcount is guaranteed to be 0)*/ else goto leave; } /* Check for rogue DNS names. */ for (i = 0; i < srvscount; i++) { if (!is_valid_domain_name (srvs[i].target)) { err = gpg_error (GPG_ERR_DNS_ADDRESS); log_error ("rogue openpgpkey SRV record for '%s'\n", domain); xfree (srvs); goto leave; } } /* Find the first target which also ends in DOMAIN or is equal * to DOMAIN. */ domainlen = strlen (domain); for (i = 0; i < srvscount; i++) { if (DBG_DNS) log_debug ("srv: trying '%s:%hu'\n", srvs[i].target, srvs[i].port); targetlen = strlen (srvs[i].target); if ((targetlen > domainlen + 1 && srvs[i].target[targetlen - domainlen - 1] == '.' && !ascii_strcasecmp (srvs[i].target + targetlen - domainlen, domain)) || (targetlen == domainlen && !ascii_strcasecmp (srvs[i].target, domain))) { /* found. */ domainbuf = xtrystrdup (srvs[i].target); if (!domainbuf) { err = gpg_error_from_syserror (); xfree (srvs); goto leave; } domain = domainbuf; if (srvs[i].port) snprintf (portstr, sizeof portstr, ":%hu", srvs[i].port); break; } } xfree (srvs); } /* Prepare the hash of the local part. */ gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, mbox, strlen (mbox)); encodedhash = zb32_encode (sha1buf, 8*20); if (!encodedhash) { err = gpg_error_from_syserror (); goto leave; } if (opt_submission_addr) { uri = strconcat ("https://", domain, portstr, "/.well-known/openpgpkey/", subdomain_mode? domain_orig : "", subdomain_mode? "/" : "", "submission-address", NULL); } else if (opt_policy_flags) { uri = strconcat ("https://", domain, portstr, "/.well-known/openpgpkey/", subdomain_mode? domain_orig : "", subdomain_mode? "/" : "", "policy", NULL); } else { char *escapedmbox; escapedmbox = http_escape_string (mbox, "%;?&=+#"); if (escapedmbox) { uri = strconcat ("https://", domain, portstr, "/.well-known/openpgpkey/", subdomain_mode? domain_orig : "", subdomain_mode? "/" : "", "hu/", encodedhash, "?l=", escapedmbox, NULL); xfree (escapedmbox); no_log = 1; if (uri) { err = dirmngr_status_printf (ctrl, "SOURCE", "https://%s%s", domain, portstr); if (err) goto leave; } } } if (!uri) { err = gpg_error_from_syserror (); goto leave; } /* Setup an output stream and perform the get. */ { estream_t outfp; outfp = ctx? es_fopencookie (ctx, "w", data_line_cookie_functions) : NULL; if (!outfp && ctx) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { if (ctrl->server_local) { if (no_log) ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; } err = ks_action_fetch (ctrl, uri, outfp); es_fclose (outfp); if (ctrl->server_local) ctrl->server_local->inhibit_data_logging = 0; /* Register the result under the domain name of MBOX. */ switch (gpg_err_code (err)) { case 0: domaininfo_set_wkd_supported (domain_orig); break; case GPG_ERR_NO_NAME: /* There is no such domain. */ domaininfo_set_no_name (domain_orig); break; case GPG_ERR_NO_DATA: if (is_wkd_query && ctrl->server_local) { /* Mark that and schedule a check. */ domaininfo_set_wkd_not_found (domain_orig); workqueue_add_task (task_check_wkd_support, domain_orig, ctrl->server_local->session_id, 1); } else if (opt_policy_flags) /* No policy file - no support. */ domaininfo_set_wkd_not_supported (domain_orig); break; default: /* Don't register other errors. */ break; } } } leave: xfree (uri); xfree (encodedhash); xfree (mbox); xfree (domainbuf); return err; } static const char hlp_wkd_get[] = "WKD_GET [--submission-address|--policy-flags] \n" "\n" "Return the key or other info for \n" "from the Web Key Directory."; static gpg_error_t cmd_wkd_get (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; err = proc_wkd_get (ctrl, ctx, line); return leave_cmd (ctx, err); } /* A task to check whether DOMAIN supports WKD. This is done by * checking whether the policy flags file can be read. */ static const char * task_check_wkd_support (ctrl_t ctrl, const char *domain) { char *string; if (!ctrl || !domain) return "check_wkd_support"; string = strconcat ("--policy-flags foo@", domain, NULL); if (!string) log_error ("%s: %s\n", __func__, gpg_strerror (gpg_error_from_syserror ())); else { proc_wkd_get (ctrl, NULL, string); xfree (string); } return NULL; } static const char hlp_ldapserver[] = "LDAPSERVER [--clear] \n" "\n" "Add a new LDAP server to the list of configured LDAP servers.\n" "DATA is in the same format as expected in the configure file.\n" "An optional prefix \"ldap:\" is allowed. With no args all\n" "configured ldapservers are listed. Option --clear removes all\n" "servers configured in this session."; static gpg_error_t cmd_ldapserver (assuan_context_t ctx, char *line) { #if USE_LDAP ctrl_t ctrl = assuan_get_pointer (ctx); ldap_server_t server; ldap_server_t *last_next_p; int clear_flag; clear_flag = has_option (line, "--clear"); line = skip_options (line); while (spacep (line)) line++; if (clear_flag) { #if USE_LDAP ldapserver_list_free (ctrl->server_local->ldapservers); #endif /*USE_LDAP*/ ctrl->server_local->ldapservers = NULL; } if (!*line && clear_flag) return leave_cmd (ctx, 0); if (!*line) { /* List all ldapservers. */ struct ldapserver_iter ldapserver_iter; char *tmpstr; char portstr[20]; for (ldapserver_iter_begin (&ldapserver_iter, ctrl); !ldapserver_iter_end_p (&ldapserver_iter); ldapserver_iter_next (&ldapserver_iter)) { server = ldapserver_iter.server; if (server->port) snprintf (portstr, sizeof portstr, "%d", server->port); else *portstr = 0; tmpstr = xtryasprintf ("ldap:%s:%s:%s:%s:%s:%s%s:", server->host? server->host : "", portstr, server->user? server->user : "", server->pass? "*****": "", server->base? server->base : "", server->starttls ? "starttls" : server->ldap_over_tls ? "ldaptls" : "none", server->ntds ? ",ntds" : ""); if (!tmpstr) return leave_cmd (ctx, gpg_error_from_syserror ()); dirmngr_status (ctrl, "LDAPSERVER", tmpstr, NULL); xfree (tmpstr); } return leave_cmd (ctx, 0); } /* Skip an "ldap:" prefix unless it is a valid ldap url. */ if (!strncmp (line, "ldap:", 5) && !(line[5] == '/' && line[6] == '/')) line += 5; server = ldapserver_parse_one (line, NULL, 0); if (! server) return leave_cmd (ctx, gpg_error (GPG_ERR_INV_ARG)); last_next_p = &ctrl->server_local->ldapservers; while (*last_next_p) last_next_p = &(*last_next_p)->next; *last_next_p = server; return leave_cmd (ctx, 0); #else (void)line; return leave_cmd (ctx, gpg_error (GPG_ERR_NOT_IMPLEMENTED)); #endif } static const char hlp_isvalid[] = "ISVALID [--only-ocsp] [--force-default-responder]" " []\n" "\n" "This command checks whether the certificate identified by the\n" "certificate_id is valid. This is done by consulting CRLs or\n" "whatever has been configured. Note, that the returned error codes\n" "are from gpg-error.h. The command may callback using the inquire\n" "function. See the manual for details.\n" "\n" "The CERTIFICATE_ID is a hex encoded string consisting of two parts,\n" "delimited by a single dot. The first part is the SHA-1 hash of the\n" "issuer name and the second part the serial number.\n" "\n" "If an OCSP check is desired CERTIFICATE_FPR with the hex encoded\n" "fingerprint of the certificate is required. In this case an OCSP\n" "request is done before consulting the CRL.\n" "\n" "If the option --only-ocsp is given, no fallback to a CRL check will\n" "be used.\n" "\n" "If the option --force-default-responder is given, only the default\n" "OCSP responder will be used and any other methods of obtaining an\n" "OCSP responder URL won't be used."; static gpg_error_t cmd_isvalid (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *issuerhash, *serialno, *fpr; gpg_error_t err; int did_inquire = 0; int ocsp_mode = 0; int only_ocsp; int force_default_responder; only_ocsp = has_option (line, "--only-ocsp"); force_default_responder = has_option (line, "--force-default-responder"); line = skip_options (line); /* We need to work on a copy of the line because that same Assuan * context may be used for an inquiry. That is because Assuan * reuses its line buffer. */ issuerhash = xstrdup (line); serialno = strchr (issuerhash, '.'); if (!serialno) { xfree (issuerhash); return leave_cmd (ctx, PARM_ERROR (_("serialno missing in cert ID"))); } *serialno++ = 0; if (strlen (issuerhash) != 40) { xfree (issuerhash); return leave_cmd (ctx, PARM_ERROR ("cert ID is too short")); } fpr = strchr (serialno, ' '); while (fpr && spacep (fpr)) fpr++; if (fpr && *fpr) { char *endp = strchr (fpr, ' '); if (endp) *endp = 0; if (strlen (fpr) != 40) { xfree (issuerhash); return leave_cmd (ctx, PARM_ERROR ("fingerprint too short")); } ocsp_mode = 1; } again: if (ocsp_mode) { + gnupg_isotime_t revoked_at; + const char *reason; + /* Note, that we currently ignore the supplied fingerprint FPR; * instead ocsp_isvalid does an inquire to ask for the cert. * The fingerprint may eventually be used to lookup the * certificate in a local cache. */ if (!opt.allow_ocsp) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else - err = ocsp_isvalid (ctrl, NULL, NULL, force_default_responder); + err = ocsp_isvalid (ctrl, NULL, NULL, force_default_responder, + revoked_at, &reason); + + if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED) + dirmngr_status_printf (ctrl, "REVOCATIONINFO", "%s %s", + revoked_at, reason); if (gpg_err_code (err) == GPG_ERR_CONFIGURATION && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR) { /* No default responder configured - fallback to CRL. */ if (!only_ocsp) log_info ("falling back to CRL check\n"); ocsp_mode = 0; goto again; } } else if (only_ocsp) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else { switch (crl_cache_isvalid (ctrl, issuerhash, serialno, ctrl->force_crl_refresh)) { case CRL_CACHE_VALID: err = 0; break; case CRL_CACHE_INVALID: err = gpg_error (GPG_ERR_CERT_REVOKED); break; case CRL_CACHE_DONTKNOW: if (did_inquire) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else if (!(err = inquire_cert_and_load_crl (ctx))) { did_inquire = 1; goto again; } break; case CRL_CACHE_CANTUSE: err = gpg_error (GPG_ERR_NO_CRL_KNOWN); break; default: log_fatal ("crl_cache_isvalid returned invalid code\n"); } } xfree (issuerhash); return leave_cmd (ctx, err); } /* If the line contains a SHA-1 fingerprint as the first argument, return the FPR vuffer on success. The function checks that the fingerprint consists of valid characters and prints and error message if it does not and returns NULL. Fingerprints are considered optional and thus no explicit error is returned. NULL is also returned if there is no fingerprint at all available. FPR must be a caller provided buffer of at least 20 bytes. Note that colons within the fingerprint are allowed to separate 2 hex digits; this allows for easier cutting and pasting using the usual fingerprint rendering. */ static unsigned char * get_fingerprint_from_line (const char *line, unsigned char *fpr) { const char *s; int i; for (s=line, i=0; *s && *s != ' '; s++ ) { if ( hexdigitp (s) && hexdigitp (s+1) ) { if ( i >= 20 ) return NULL; /* Fingerprint too long. */ fpr[i++] = xtoi_2 (s); s++; } else if ( *s != ':' ) return NULL; /* Invalid. */ } if ( i != 20 ) return NULL; /* Fingerprint to short. */ return fpr; } static const char hlp_checkcrl[] = "CHECKCRL []\n" "\n" "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n" "entire X.509 certificate blob) is valid or not by consulting the\n" "CRL responsible for this certificate. If the fingerprint has not\n" "been given or the certificate is not known, the function \n" "inquires the certificate using an\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request (which should match FINGERPRINT) as a binary blob.\n" "Processing then takes place without further interaction; in\n" "particular dirmngr tries to locate other required certificate by\n" "its own mechanism which includes a local certificate store as well\n" "as a list of trusted root certificates.\n" "\n" "The return value is the usual gpg-error code or 0 for ducesss;\n" "i.e. the certificate validity has been confirmed by a valid CRL."; static gpg_error_t cmd_checkcrl (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char fprbuffer[20], *fpr; ksba_cert_t cert; fpr = get_fingerprint_from_line (line, fprbuffer); cert = fpr? get_cert_byfpr (fpr) : NULL; if (!cert) { /* We do not have this certificate yet or the fingerprint has not been given. Inquire it from the client. */ unsigned char *value = NULL; size_t valuelen; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; } assert (cert); err = crl_cache_cert_isvalid (ctrl, cert, ctrl->force_crl_refresh); if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN) { err = crl_cache_reload_crl (ctrl, cert); if (!err) err = crl_cache_cert_isvalid (ctrl, cert, 0); } leave: ksba_cert_release (cert); return leave_cmd (ctx, err); } static const char hlp_checkocsp[] = "CHECKOCSP [--force-default-responder] []\n" "\n" "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n" "entire X.509 certificate blob) is valid or not by asking an OCSP\n" "responder responsible for this certificate. The optional\n" "fingerprint may be used for a quick check in case an OCSP check has\n" "been done for this certificate recently (we always cache OCSP\n" "responses for a couple of minutes). If the fingerprint has not been\n" "given or there is no cached result, the function inquires the\n" "certificate using an\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request (which should match FINGERPRINT) as a binary blob.\n" "Processing then takes place without further interaction; in\n" "particular dirmngr tries to locate other required certificates by\n" "its own mechanism which includes a local certificate store as well\n" "as a list of trusted root certificates.\n" "\n" "If the option --force-default-responder is given, only the default\n" "OCSP responder will be used and any other methods of obtaining an\n" "OCSP responder URL won't be used.\n" "\n" "The return value is the usual gpg-error code or 0 for ducesss;\n" "i.e. the certificate validity has been confirmed by a valid CRL."; static gpg_error_t cmd_checkocsp (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char fprbuffer[20], *fpr; ksba_cert_t cert; int force_default_responder; + gnupg_isotime_t revoked_at; + const char *reason; force_default_responder = has_option (line, "--force-default-responder"); line = skip_options (line); fpr = get_fingerprint_from_line (line, fprbuffer); cert = fpr? get_cert_byfpr (fpr) : NULL; if (!cert) { /* We do not have this certificate yet or the fingerprint has not been given. Inquire it from the client. */ unsigned char *value = NULL; size_t valuelen; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; } - assert (cert); + log_assert (cert); if (!opt.allow_ocsp) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else - err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder); + err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder, + revoked_at, &reason); + + if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED) + dirmngr_status_printf (ctrl, "REVOCATIONINFO", "%s %s", + revoked_at, reason); + leave: ksba_cert_release (cert); return leave_cmd (ctx, err); } static int lookup_cert_by_url (assuan_context_t ctx, const char *url) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; unsigned char *value = NULL; size_t valuelen; /* Fetch single certificate given it's URL. */ err = fetch_cert_by_url (ctrl, url, &value, &valuelen); if (err) { log_error (_("fetch_cert_by_url failed: %s\n"), gpg_strerror (err)); goto leave; } /* Send the data, flush the buffer and then send an END. */ err = assuan_send_data (ctx, value, valuelen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); if (err) { log_error (_("error sending data: %s\n"), gpg_strerror (err)); goto leave; } leave: return err; } /* Send the certificate, flush the buffer and then send an END. */ static gpg_error_t return_one_cert (void *opaque, ksba_cert_t cert) { assuan_context_t ctx = opaque; gpg_error_t err; const unsigned char *der; size_t derlen; der = ksba_cert_get_image (cert, &derlen); if (!der) err = gpg_error (GPG_ERR_INV_CERT_OBJ); else { err = assuan_send_data (ctx, der, derlen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); } if (err) log_error (_("error sending data: %s\n"), gpg_strerror (err)); return err; } /* Lookup certificates from the internal cache or using the ldap servers. */ static int lookup_cert_by_pattern (assuan_context_t ctx, char *line, int single, int cache_only) { gpg_error_t err = 0; char *p; strlist_t sl, list = NULL; int truncated = 0, truncation_forced = 0; int count = 0; int local_count = 0; #if USE_LDAP ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *value = NULL; size_t valuelen; struct ldapserver_iter ldapserver_iter; cert_fetch_context_t fetch_context; #endif /*USE_LDAP*/ int any_no_data = 0; /* Break the line down into an STRLIST */ for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { err = gpg_error_from_errno (errno); goto leave; } memset (sl, 0, sizeof *sl); strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } /* First look through the internal cache. The certificates returned here are not counted towards the truncation limit. */ if (single && !cache_only) ; /* Do not read from the local cache in this case. */ else { for (sl=list; sl; sl = sl->next) { err = get_certs_bypattern (sl->d, return_one_cert, ctx); if (!err) local_count++; if (!err && single) goto ready; if (gpg_err_code (err) == GPG_ERR_NO_DATA || gpg_err_code (err) == GPG_ERR_NOT_FOUND) { err = 0; if (cache_only) any_no_data = 1; } else if (gpg_err_code (err) == GPG_ERR_INV_NAME && !cache_only) { /* No real fault because the internal pattern lookup can't yet cope with all types of pattern. */ err = 0; } if (err) goto ready; } } /* Loop over all configured servers unless we want only the certificates from the cache. */ #if USE_LDAP for (ldapserver_iter_begin (&ldapserver_iter, ctrl); !cache_only && !ldapserver_iter_end_p (&ldapserver_iter) && ldapserver_iter.server->host && !truncation_forced; ldapserver_iter_next (&ldapserver_iter)) { ldap_server_t ldapserver = ldapserver_iter.server; if (DBG_LOOKUP) log_debug ("cmd_lookup: trying %s:%d base=%s\n", ldapserver->host, ldapserver->port, ldapserver->base?ldapserver->base : "[default]"); /* Fetch certificates matching pattern */ err = start_cert_fetch (ctrl, &fetch_context, list, ldapserver); if ( gpg_err_code (err) == GPG_ERR_NO_DATA ) { if (DBG_LOOKUP) log_debug ("cmd_lookup: no data\n"); err = 0; any_no_data = 1; continue; } if (err) { log_error (_("start_cert_fetch failed: %s\n"), gpg_strerror (err)); goto leave; } /* Fetch the certificates for this query. */ while (!truncation_forced) { xfree (value); value = NULL; err = fetch_next_cert (fetch_context, &value, &valuelen); if (gpg_err_code (err) == GPG_ERR_NO_DATA ) { err = 0; any_no_data = 1; break; /* Ready. */ } if (gpg_err_code (err) == GPG_ERR_TRUNCATED) { truncated = 1; err = 0; break; /* Ready. */ } if (gpg_err_code (err) == GPG_ERR_EOF) { err = 0; break; /* Ready. */ } if (!err && !value) { err = gpg_error (GPG_ERR_BUG); goto leave; } if (err) { log_error (_("fetch_next_cert failed: %s\n"), gpg_strerror (err)); end_cert_fetch (fetch_context); goto leave; } if (DBG_LOOKUP) log_debug ("cmd_lookup: returning one cert%s\n", truncated? " (truncated)":""); /* Send the data, flush the buffer and then send an END line as a certificate delimiter. */ err = assuan_send_data (ctx, value, valuelen); if (!err) err = assuan_send_data (ctx, NULL, 0); if (!err) err = assuan_write_line (ctx, "END"); if (err) { log_error (_("error sending data: %s\n"), gpg_strerror (err)); end_cert_fetch (fetch_context); goto leave; } if (++count >= opt.max_replies ) { truncation_forced = 1; log_info (_("max_replies %d exceeded\n"), opt.max_replies ); } if (single) break; } end_cert_fetch (fetch_context); } #endif /*USE_LDAP*/ ready: if (truncated || truncation_forced) { char str[50]; sprintf (str, "%d", count); assuan_write_status (ctx, "TRUNCATED", str); } if (!err && !count && !local_count && any_no_data) err = gpg_error (GPG_ERR_NO_DATA); leave: free_strlist (list); return err; } static const char hlp_lookup[] = "LOOKUP [--url] [--single] [--cache-only] \n" "\n" "Lookup certificates matching PATTERN. With --url the pattern is\n" "expected to be one URL.\n" "\n" "If --url is not given: To allow for multiple patterns (which are ORed)\n" "quoting is required: Spaces are translated to \"+\" or \"%20\";\n" "obviously this requires that the usual escape quoting rules are applied.\n" "\n" "If --url is given no special escaping is required because URLs are\n" "already escaped this way.\n" "\n" "If --single is given the first and only the first match will be\n" "returned. If --cache-only is _not_ given, no local query will be\n" "done.\n" "\n" "If --cache-only is given no external lookup is done so that only\n" "certificates from the cache may get returned."; static gpg_error_t cmd_lookup (assuan_context_t ctx, char *line) { gpg_error_t err; int lookup_url, single, cache_only; lookup_url = has_leading_option (line, "--url"); single = has_leading_option (line, "--single"); cache_only = has_leading_option (line, "--cache-only"); line = skip_options (line); if (lookup_url && cache_only) err = gpg_error (GPG_ERR_NOT_FOUND); else if (lookup_url && single) err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); else if (lookup_url) err = lookup_cert_by_url (ctx, line); else err = lookup_cert_by_pattern (ctx, line, single, cache_only); return leave_cmd (ctx, err); } static const char hlp_loadcrl[] = "LOADCRL [--url] \n" "\n" "Load the CRL in the file with name FILENAME into our cache. Note\n" "that FILENAME should be given with an absolute path because\n" "Dirmngrs cwd is not known. With --url the CRL is directly loaded\n" "from the given URL.\n" "\n" "This command is usually used by gpgsm using the invocation \"gpgsm\n" "--call-dirmngr loadcrl \". A direct invocation of Dirmngr\n" "is not useful because gpgsm might need to callback gpgsm to ask for\n" "the CA's certificate."; static gpg_error_t cmd_loadcrl (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int use_url = has_leading_option (line, "--url"); line = skip_options (line); if (use_url) { ksba_reader_t reader; err = crl_fetch (ctrl, line, &reader); if (err) log_error (_("fetching CRL from '%s' failed: %s\n"), line, gpg_strerror (err)); else { err = crl_cache_insert (ctrl, line, reader); if (err) log_error (_("processing CRL from '%s' failed: %s\n"), line, gpg_strerror (err)); crl_close_reader (reader); } } else { char *buf; buf = xtrymalloc (strlen (line)+1); if (!buf) err = gpg_error_from_syserror (); else { strcpy_escaped_plus (buf, line); err = crl_cache_load (ctrl, buf); xfree (buf); } } return leave_cmd (ctx, err); } static const char hlp_listcrls[] = "LISTCRLS\n" "\n" "List the content of all CRLs in a readable format. This command is\n" "usually used by gpgsm using the invocation \"gpgsm --call-dirmngr\n" "listcrls\". It may also be used directly using \"dirmngr\n" "--list-crls\"."; static gpg_error_t cmd_listcrls (assuan_context_t ctx, char *line) { gpg_error_t err; estream_t fp; (void)line; fp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!fp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { err = crl_cache_list (fp); es_fclose (fp); } return leave_cmd (ctx, err); } static const char hlp_cachecert[] = "CACHECERT\n" "\n" "Put a certificate into the internal cache. This command might be\n" "useful if a client knows in advance certificates required for a\n" "test and wants to make sure they get added to the internal cache.\n" "It is also helpful for debugging. To get the actual certificate,\n" "this command immediately inquires it using\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request as a binary blob."; static gpg_error_t cmd_cachecert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; unsigned char *value = NULL; size_t valuelen; (void)line; err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; err = cache_cert (cert); leave: ksba_cert_release (cert); return leave_cmd (ctx, err); } static const char hlp_validate[] = "VALIDATE [--systrust] [--tls] [--no-crl]\n" "\n" "Validate a certificate using the certificate validation function\n" "used internally by dirmngr. This command is only useful for\n" "debugging. To get the actual certificate, this command immediately\n" "inquires it using\n" "\n" " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" "request as a binary blob. The option --tls modifies this by asking\n" "for list of certificates with\n" "\n" " INQUIRE CERTLIST\n" "\n" "Here the first certificate is the target certificate, the remaining\n" "certificates are suggested intermediary certificates. All certificates\n" "need to be PEM encoded.\n" "\n" "The option --systrust changes the behaviour to include the system\n" "provided root certificates as trust anchors. The option --no-crl\n" "skips CRL checks"; static gpg_error_t cmd_validate (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; certlist_t certlist = NULL; unsigned char *value = NULL; size_t valuelen; int systrust_mode, tls_mode, no_crl; systrust_mode = has_option (line, "--systrust"); tls_mode = has_option (line, "--tls"); no_crl = has_option (line, "--no-crl"); line = skip_options (line); if (tls_mode) err = assuan_inquire (ctrl->server_local->assuan_ctx, "CERTLIST", &value, &valuelen, MAX_CERTLIST_LENGTH); else err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); else if (tls_mode) { estream_t fp; fp = es_fopenmem_init (0, "rb", value, valuelen); if (!fp) err = gpg_error_from_syserror (); else { err = read_certlist_from_stream (&certlist, fp); es_fclose (fp); if (!err && !certlist) err = gpg_error (GPG_ERR_MISSING_CERT); if (!err) { /* Extract the first certificate from the list. */ cert = certlist->cert; ksba_cert_ref (cert); } } } else { err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, value, valuelen); } xfree (value); if(err) goto leave; if (!tls_mode) { /* If we have this certificate already in our cache, use the * cached version for validation because this will take care of * any cached results. We don't need to do this in tls mode * because this has already been done for certificate in a * certlist_t. */ unsigned char fpr[20]; ksba_cert_t tmpcert; cert_compute_fpr (cert, fpr); tmpcert = get_cert_byfpr (fpr); if (tmpcert) { ksba_cert_release (cert); cert = tmpcert; } } /* Quick hack to make verification work by inserting the supplied * certs into the cache. */ if (tls_mode && certlist) { certlist_t cl; for (cl = certlist->next; cl; cl = cl->next) cache_cert (cl->cert); } err = validate_cert_chain (ctrl, cert, NULL, (VALIDATE_FLAG_TRUST_CONFIG | (tls_mode ? VALIDATE_FLAG_TLS : 0) | (systrust_mode ? VALIDATE_FLAG_TRUST_SYSTEM : 0) | (no_crl ? VALIDATE_FLAG_NOCRLCHECK : 0)), NULL); leave: ksba_cert_release (cert); release_certlist (certlist); return leave_cmd (ctx, err); } /* Parse an keyserver URI and store it in a new uri item which is returned at R_ITEM. On error return an error code. */ static gpg_error_t make_keyserver_item (const char *uri, uri_item_t *r_item) { gpg_error_t err; uri_item_t item; char *tmpstr = NULL; #if USE_LDAP const char *s; #endif *r_item = NULL; /* We used to have DNS CNAME redirection from the URLs below to * sks-keyserver. pools. The idea was to allow for a quick way to * switch to a different set of pools. The problem with that * approach is that TLS needs to verify the hostname and - because * DNS is not secured - it can only check the user supplied hostname * and not a hostname from a CNAME RR. Thus the final server all * need to have certificates with the actual pool name as well as * for keys.gnupg.net - that would render the advantage of * keys.gnupg.net useless and so we better give up on this. Because * the keys.gnupg.net URL are still in widespread use we do a static * mapping here. */ if (!strcmp (uri, "hkps://keys.gnupg.net") || !strcmp (uri, "keys.gnupg.net")) uri = "hkps://keyserver.ubuntu.com"; else if (!strcmp (uri, "https://keys.gnupg.net")) uri = "hkps://keyserver.ubuntu.com"; else if (!strcmp (uri, "hkp://keys.gnupg.net")) uri = "hkp://keyserver.ubuntu.com"; else if (!strcmp (uri, "http://keys.gnupg.net")) uri = "hkp://keyserver.ubuntu.com:80"; else if (!strcmp (uri, "hkps://http-keys.gnupg.net") || !strcmp (uri, "http-keys.gnupg.net")) uri = "hkps://keyserver.ubuntu.com"; else if (!strcmp (uri, "https://http-keys.gnupg.net")) uri = "hkps://keyserver.ubuntu.com"; else if (!strcmp (uri, "hkp://http-keys.gnupg.net")) uri = "hkp://keyserver.ubuntu.com"; else if (!strcmp (uri, "http://http-keys.gnupg.net")) uri = "hkp://keyserver.ubuntu.com:80"; item = xtrymalloc (sizeof *item + strlen (uri)); if (!item) return gpg_error_from_syserror (); item->next = NULL; item->parsed_uri = NULL; strcpy (item->uri, uri); #if USE_LDAP if (!strncmp (uri, "ldap:", 5) && !(uri[5] == '/' && uri[6] == '/')) { /* Special ldap scheme given. This differs from a valid ldap * scheme in that no double slash follows.. Use http_parse_uri * to put it as opaque value into parsed_uri. */ tmpstr = strconcat ("opaque:", uri+5, NULL); if (!tmpstr) err = gpg_error_from_syserror (); else err = http_parse_uri (&item->parsed_uri, tmpstr, 0); } else if ((s=strchr (uri, ':')) && !(s[1] == '/' && s[2] == '/')) { /* No valid scheme given. Use http_parse_uri to put the string * as opaque value into parsed_uri. */ tmpstr = strconcat ("opaque:", uri, NULL); if (!tmpstr) err = gpg_error_from_syserror (); else err = http_parse_uri (&item->parsed_uri, tmpstr, 0); } else if (ldap_uri_p (uri)) { int fixup = 0; /* Fixme: We should get rid of that parser and replace it with * our generic (http) URI parser. */ /* If no port has been specified and the scheme ist ldaps we use * our idea of the default port because the standard LDAP URL * parser would use 636 here. This is because we redefined * ldaps to mean starttls. */ #ifdef HAVE_W32_SYSTEM if (!strcmp (uri, "ldap:///")) fixup = 1; else #endif if (!http_parse_uri (&item->parsed_uri,uri,HTTP_PARSE_NO_SCHEME_CHECK)) { if (!item->parsed_uri->port && !strcmp (item->parsed_uri->scheme, "ldaps")) fixup = 2; http_release_parsed_uri (item->parsed_uri); item->parsed_uri = NULL; } err = ldap_parse_uri (&item->parsed_uri, uri); if (!err && fixup == 1) item->parsed_uri->ad_current = 1; else if (!err && fixup == 2) item->parsed_uri->port = 389; } else #endif /* USE_LDAP */ { err = http_parse_uri (&item->parsed_uri, uri, HTTP_PARSE_NO_SCHEME_CHECK); } xfree (tmpstr); if (err) xfree (item); else *r_item = item; return err; } /* If no keyserver is stored in CTRL but a global keyserver has been set, put that global keyserver into CTRL. We need use this function to help migrate from the old gpg based keyserver configuration to the new dirmngr based configuration. */ static gpg_error_t ensure_keyserver (ctrl_t ctrl) { gpg_error_t err; uri_item_t item; uri_item_t onion_items = NULL; uri_item_t plain_items = NULL; uri_item_t ui; strlist_t sl; if (ctrl->server_local->keyservers) return 0; /* Already set for this session. */ if (!opt.keyserver) { /* No global option set. Fall back to default: */ return make_keyserver_item (DIRMNGR_DEFAULT_KEYSERVER, &ctrl->server_local->keyservers); } for (sl = opt.keyserver; sl; sl = sl->next) { err = make_keyserver_item (sl->d, &item); if (err) goto leave; if (item->parsed_uri->onion) { item->next = onion_items; onion_items = item; } else { item->next = plain_items; plain_items = item; } } /* Decide which to use. Note that the session has no keyservers yet set. */ if (onion_items && !onion_items->next && plain_items && !plain_items->next) { /* If there is just one onion and one plain keyserver given, we take only one depending on whether Tor is running or not. */ if (!dirmngr_never_use_tor_p () && is_tor_running (ctrl)) { ctrl->server_local->keyservers = onion_items; onion_items = NULL; } else { ctrl->server_local->keyservers = plain_items; plain_items = NULL; } } else if (dirmngr_never_use_tor_p () || !is_tor_running (ctrl)) { /* Tor is not running. It does not make sense to add Onion addresses. */ ctrl->server_local->keyservers = plain_items; plain_items = NULL; } else { /* In all other cases add all keyservers. */ ctrl->server_local->keyservers = onion_items; onion_items = NULL; for (ui = ctrl->server_local->keyservers; ui && ui->next; ui = ui->next) ; if (ui) ui->next = plain_items; else ctrl->server_local->keyservers = plain_items; plain_items = NULL; } leave: release_uri_item_list (onion_items); release_uri_item_list (plain_items); return err; } static const char hlp_keyserver[] = "KEYSERVER [] [|]\n" "Options are:\n" " --help\n" " --clear Remove all configured keyservers\n" " --resolve Resolve HKP host names and rotate\n" " --hosttable Print table of known hosts and pools\n" " --dead Mark as dead\n" " --alive Mark as alive\n" "\n" "If called without arguments list all configured keyserver URLs.\n" "If called with an URI add this as keyserver. Note that keyservers\n" "are configured on a per-session base. A default keyserver may already be\n" "present, thus the \"--clear\" option must be used to get full control.\n" "If \"--clear\" and an URI are used together the clear command is\n" "obviously executed first. A RESET command does not change the list\n" "of configured keyservers."; static gpg_error_t cmd_keyserver (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int clear_flag, add_flag, help_flag, host_flag, resolve_flag; int dead_flag, alive_flag; uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it is always initialized. */ clear_flag = has_option (line, "--clear"); help_flag = has_option (line, "--help"); resolve_flag = has_option (line, "--resolve"); host_flag = has_option (line, "--hosttable"); dead_flag = has_option (line, "--dead"); alive_flag = has_option (line, "--alive"); line = skip_options (line); add_flag = !!*line; if (help_flag) { err = ks_action_help (ctrl, line); goto leave; } if (resolve_flag) { err = ensure_keyserver (ctrl); if (err) { assuan_set_error (ctx, err, "Bad keyserver configuration in dirmngr.conf"); goto leave; } err = ks_action_resolve (ctrl, ctrl->server_local->keyservers); if (err) goto leave; } if (alive_flag && dead_flag) { err = set_error (GPG_ERR_ASS_PARAMETER, "no support for zombies"); goto leave; } if (dead_flag) { err = check_owner_permission (ctx, "no permission to use --dead"); if (err) goto leave; } if (alive_flag || dead_flag) { if (!*line) { err = set_error (GPG_ERR_ASS_PARAMETER, "name of host missing"); goto leave; } err = ks_hkp_mark_host (ctrl, line, alive_flag); if (err) goto leave; } if (host_flag) { err = ks_hkp_print_hosttable (ctrl); if (err) goto leave; } if (resolve_flag || host_flag || alive_flag || dead_flag) goto leave; if (add_flag) { err = make_keyserver_item (line, &item); if (err) goto leave; } if (clear_flag) release_ctrl_keyservers (ctrl); if (add_flag) { item->next = ctrl->server_local->keyservers; ctrl->server_local->keyservers = item; } if (!add_flag && !clear_flag && !help_flag) { /* List configured keyservers. However, we first add a global keyserver. */ uri_item_t u; err = ensure_keyserver (ctrl); if (err) { assuan_set_error (ctx, err, "Bad keyserver configuration in dirmngr.conf"); goto leave; } for (u=ctrl->server_local->keyservers; u; u = u->next) dirmngr_status (ctrl, "KEYSERVER", u->uri, NULL); } err = 0; leave: return leave_cmd (ctx, err); } static const char hlp_ks_search[] = "KS_SEARCH {}\n" "\n" "Search the configured OpenPGP keyservers (see command KEYSERVER)\n" "for keys matching PATTERN"; static gpg_error_t cmd_ks_search (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; strlist_t list, sl; char *p; estream_t outfp; if (has_option (line, "--quick")) ctrl->timeout = opt.connect_quick_timeout; line = skip_options (line); /* Break the line down into an strlist. Each pattern is percent-plus escaped. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { err = gpg_error_from_syserror (); goto leave; } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } err = ensure_keyserver (ctrl); if (err) goto leave; /* Setup an output stream and perform the search. */ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { err = ks_action_search (ctrl, ctrl->server_local->keyservers, list, outfp); es_fclose (outfp); } leave: free_strlist (list); return leave_cmd (ctx, err); } static const char hlp_ks_get[] = "KS_GET [--quick] [--ldap] [--first|--next] {}\n" "\n" "Get the keys matching PATTERN from the configured OpenPGP keyservers\n" "(see command KEYSERVER). Each pattern should be a keyid, a fingerprint,\n" "or an exact name indicated by the '=' prefix. Option --quick uses a\n" "shorter timeout; --ldap will use only ldap servers. With --first only\n" "the first item is returned; --next is used to return the next item"; static gpg_error_t cmd_ks_get (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; strlist_t list, sl; char *p; estream_t outfp; unsigned int flags = 0; if (has_option (line, "--quick")) ctrl->timeout = opt.connect_quick_timeout; if (has_option (line, "--ldap")) flags |= KS_GET_FLAG_ONLY_LDAP; if (has_option (line, "--first")) flags |= KS_GET_FLAG_FIRST; if (has_option (line, "--next")) flags |= KS_GET_FLAG_NEXT; line = skip_options (line); /* Break the line into a strlist. Each pattern is by definition percent-plus escaped. However we only support keyids and fingerprints and thus the client has no need to apply the escaping. */ list = NULL; for (p=line; *p; line = p) { while (*p && *p != ' ') p++; if (*p) *p++ = 0; if (*line) { sl = xtrymalloc (sizeof *sl + strlen (line)); if (!sl) { err = gpg_error_from_syserror (); goto leave; } sl->flags = 0; strcpy_escaped_plus (sl->d, line); sl->next = list; list = sl; } } if ((flags & KS_GET_FLAG_FIRST) && !(flags & KS_GET_FLAG_ONLY_LDAP)) { err = PARM_ERROR ("--first is only supported with --ldap"); goto leave; } if (list && list->next && (flags & KS_GET_FLAG_FIRST)) { /* ks_action_get loops over the pattern and we can't easily keep * this state. */ err = PARM_ERROR ("Only one pattern allowed with --first"); goto leave; } if (!list && (flags & KS_GET_FLAG_FIRST)) { /* Need to add a dummy pattern if no pattern is given. */ if (!add_to_strlist_try (&list, "")) { err = gpg_error_from_syserror (); goto leave; } } if ((flags & KS_GET_FLAG_NEXT)) { if (list || (flags & ~KS_GET_FLAG_NEXT)) { err = PARM_ERROR ("No pattern or other options allowed with --next"); goto leave; } /* Add a dummy pattern. */ if (!add_to_strlist_try (&list, "")) { err = gpg_error_from_syserror (); goto leave; } } err = ensure_keyserver (ctrl); if (err) goto leave; /* Setup an output stream and perform the get. */ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; err = ks_action_get (ctrl, ctrl->server_local->keyservers, list, flags, outfp); es_fclose (outfp); ctrl->server_local->inhibit_data_logging = 0; } leave: free_strlist (list); return leave_cmd (ctx, err); } static const char hlp_ks_fetch[] = "KS_FETCH \n" "\n" "Get the key(s) from URL."; static gpg_error_t cmd_ks_fetch (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; estream_t outfp; if (has_option (line, "--quick")) ctrl->timeout = opt.connect_quick_timeout; line = skip_options (line); err = ensure_keyserver (ctrl); /* FIXME: Why do we needs this here? */ if (err) goto leave; /* Setup an output stream and perform the get. */ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); if (!outfp) err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); else { ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; err = ks_action_fetch (ctrl, line, outfp); es_fclose (outfp); ctrl->server_local->inhibit_data_logging = 0; } leave: return leave_cmd (ctx, err); } static const char hlp_ks_put[] = "KS_PUT\n" "\n" "Send a key to the configured OpenPGP keyservers. The actual key material\n" "is then requested by Dirmngr using\n" "\n" " INQUIRE KEYBLOCK\n" "\n" "The client shall respond with a binary version of the keyblock (e.g.,\n" "the output of `gpg --export KEYID'). For LDAP\n" "keyservers Dirmngr may ask for meta information of the provided keyblock\n" "using:\n" "\n" " INQUIRE KEYBLOCK_INFO\n" "\n" "The client shall respond with a colon delimited info lines (the output\n" "of 'gpg --list-keys --with-colons KEYID').\n"; static gpg_error_t cmd_ks_put (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char *value = NULL; size_t valuelen; unsigned char *info = NULL; size_t infolen; /* No options for now. */ line = skip_options (line); err = ensure_keyserver (ctrl); if (err) goto leave; /* Ask for the key material. */ err = assuan_inquire (ctx, "KEYBLOCK", &value, &valuelen, MAX_KEYBLOCK_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } if (!valuelen) /* No data returned; return a comprehensible error. */ { err = gpg_error (GPG_ERR_MISSING_CERT); goto leave; } /* Ask for the key meta data. */ err = assuan_inquire (ctx, "KEYBLOCK_INFO", &info, &infolen, MAX_KEYBLOCK_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); goto leave; } /* Send the key. */ err = ks_action_put (ctrl, ctrl->server_local->keyservers, value, valuelen, info, infolen); leave: xfree (info); xfree (value); return leave_cmd (ctx, err); } static const char hlp_loadswdb[] = "LOADSWDB [--force]\n" "\n" "Load and verify the swdb.lst from the Net."; static gpg_error_t cmd_loadswdb (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; err = dirmngr_load_swdb (ctrl, has_option (line, "--force")); return leave_cmd (ctx, err); } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multi purpose command to return certain information. \n" "Supported values of WHAT are:\n" "\n" "version - Return the version of the program.\n" "pid - Return the process id of the server.\n" "tor - Return OK if running in Tor mode\n" "dnsinfo - Return info about the DNS resolver\n" "socket_name - Return the name of the socket.\n" "session_id - Return the current session_id.\n" "workqueue - Inspect the work queue\n" "getenv NAME - Return value of envvar NAME\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; char numbuf[50]; if (!strcmp (line, "version")) { const char *s = VERSION; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = dirmngr_get_current_socket_name (); err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "session_id")) { snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "tor")) { int use_tor; use_tor = dirmngr_use_tor (); if (use_tor) { if (!is_tor_running (ctrl)) err = assuan_write_status (ctx, "NO_TOR", "Tor not running"); else err = 0; if (!err) assuan_set_okay_line (ctx, use_tor == 1 ? "- Tor mode is enabled" /**/ : "- Tor mode is enforced"); } else err = set_error (GPG_ERR_FALSE, "Tor mode is NOT enabled"); } else if (!strcmp (line, "dnsinfo")) { if (standard_resolver_p ()) assuan_set_okay_line (ctx, "- Forced use of System resolver (w/o Tor support)"); else { #ifdef USE_LIBDNS assuan_set_okay_line (ctx, (recursive_resolver_p () ? "- Libdns recursive resolver" : "- Libdns stub resolver")); #else assuan_set_okay_line (ctx, "- System resolver (w/o Tor support)"); #endif } err = 0; } else if (!strcmp (line, "workqueue")) { workqueue_dump_queue (ctrl); err = 0; } else if (!strncmp (line, "getenv", 6) && (line[6] == ' ' || line[6] == '\t' || !line[6])) { line += 6; while (*line == ' ' || *line == '\t') line++; if (!*line) err = gpg_error (GPG_ERR_MISSING_VALUE); else { const char *s = getenv (line); if (!s) err = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); else err = assuan_send_data (ctx, s, strlen (s)); } } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return leave_cmd (ctx, err); } static const char hlp_killdirmngr[] = "KILLDIRMNGR\n" "\n" "This command allows a user - given sufficient permissions -\n" "to kill this dirmngr process.\n"; static gpg_error_t cmd_killdirmngr (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return 0; } static const char hlp_reloaddirmngr[] = "RELOADDIRMNGR\n" "\n" "This command is an alternative to SIGHUP\n" "to reload the configuration."; static gpg_error_t cmd_reloaddirmngr (assuan_context_t ctx, char *line) { (void)ctx; (void)line; dirmngr_sighup_action (); return 0; } static const char hlp_flushcrls[] = "FLUSHCRLS\n" "\n" "Remove all cached CRLs from memory and\n" "the file system."; static gpg_error_t cmd_flushcrls (assuan_context_t ctx, char *line) { (void)line; return leave_cmd (ctx, crl_cache_flush () ? GPG_ERR_GENERAL : 0); } /* Tell the assuan library about our commands. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "DNS_CERT", cmd_dns_cert, hlp_dns_cert }, { "WKD_GET", cmd_wkd_get, hlp_wkd_get }, { "LDAPSERVER", cmd_ldapserver, hlp_ldapserver }, { "ISVALID", cmd_isvalid, hlp_isvalid }, { "CHECKCRL", cmd_checkcrl, hlp_checkcrl }, { "CHECKOCSP", cmd_checkocsp, hlp_checkocsp }, { "LOOKUP", cmd_lookup, hlp_lookup }, { "LOADCRL", cmd_loadcrl, hlp_loadcrl }, { "LISTCRLS", cmd_listcrls, hlp_listcrls }, { "CACHECERT", cmd_cachecert, hlp_cachecert }, { "VALIDATE", cmd_validate, hlp_validate }, { "KEYSERVER", cmd_keyserver, hlp_keyserver }, { "KS_SEARCH", cmd_ks_search, hlp_ks_search }, { "KS_GET", cmd_ks_get, hlp_ks_get }, { "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch }, { "KS_PUT", cmd_ks_put, hlp_ks_put }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "LOADSWDB", cmd_loadswdb, hlp_loadswdb }, { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr }, { "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr }, { "FLUSHCRLS", cmd_flushcrls, hlp_flushcrls }, { NULL, NULL } }; int i, j, rc; for (i=j=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Note that we do not reset the list of configured keyservers. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; #if USE_LDAP ldapserver_list_free (ctrl->server_local->ldapservers); #endif /*USE_LDAP*/ ctrl->server_local->ldapservers = NULL; return 0; } /* This function is called by our assuan log handler to test whether a * log message shall really be printed. The function must return * false to inhibit the logging of MSG. CAT gives the requested log * category. MSG might be NULL. */ int dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, const char *msg) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)cat; (void)msg; if (!ctrl || !ctrl->server_local) return 1; /* Can't decide - allow logging. */ if (!ctrl->server_local->inhibit_data_logging) return 1; /* Not requested - allow logging. */ /* Disallow logging if *_now is true. */ return !ctrl->server_local->inhibit_data_logging_now; } /* Startup the server and run the main command loop. With FD = -1, * use stdin/stdout. SESSION_ID is either 0 or a unique number * identifying a session. */ void start_command_handler (assuan_fd_t fd, unsigned int session_id) { static const char hello[] = "Dirmngr " VERSION " at your service"; static char *hello_line; int rc; assuan_context_t ctx; ctrl_t ctrl; ctrl = xtrycalloc (1, sizeof *ctrl); if (ctrl) ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local); if (!ctrl || !ctrl->server_local) { log_error (_("can't allocate control structure: %s\n"), strerror (errno)); xfree (ctrl); return; } dirmngr_init_default_ctrl (ctrl); rc = assuan_new (&ctx); if (rc) { log_error (_("failed to allocate assuan context: %s\n"), gpg_strerror (rc)); dirmngr_exit (2); } if (fd == ASSUAN_INVALID_FD) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else { rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { assuan_release (ctx); log_error (_("failed to initialize the server: %s\n"), gpg_strerror(rc)); dirmngr_exit (2); } rc = register_commands (ctx); if (rc) { log_error (_("failed to the register commands with Assuan: %s\n"), gpg_strerror(rc)); dirmngr_exit (2); } if (!hello_line) { hello_line = xtryasprintf ("Home: %s\n" "Config: %s\n" "%s", gnupg_homedir (), opt.config_filename? opt.config_filename : "[none]", hello); } ctrl->server_local->assuan_ctx = ctx; assuan_set_pointer (ctx, ctrl); assuan_set_hello_line (ctx, hello_line); assuan_register_option_handler (ctx, option_handler); assuan_register_reset_notify (ctx, reset_notify); ctrl->server_local->session_id = session_id; for (;;) { rc = assuan_accept (ctx); if (rc == -1) break; if (rc) { log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc)); break; } #ifndef HAVE_W32_SYSTEM if (opt.verbose) { assuan_peercred_t peercred; if (!assuan_get_peercred (ctx, &peercred)) log_info ("connection from process %ld (%ld:%ld)\n", (long)peercred->pid, (long)peercred->uid, (long)peercred->gid); } #endif rc = assuan_process (ctx); if (rc) { log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc)); continue; } } #if USE_LDAP ldap_wrapper_connection_cleanup (ctrl); ldapserver_list_free (ctrl->server_local->ldapservers); #endif /*USE_LDAP*/ ctrl->server_local->ldapservers = NULL; release_ctrl_keyservers (ctrl); ctrl->server_local->assuan_ctx = NULL; assuan_release (ctx); if (ctrl->server_local->stopme) dirmngr_exit (0); if (ctrl->refcount) log_error ("oops: connection control structure still referenced (%d)\n", ctrl->refcount); else { #if USE_LDAP ks_ldap_free_state (ctrl->ks_get_state); ctrl->ks_get_state = NULL; #endif release_ctrl_ocsp_certs (ctrl); xfree (ctrl->server_local); dirmngr_deinit_default_ctrl (ctrl); xfree (ctrl); } } /* Send a status line back to the client. KEYWORD is the status keyword, the optional string arguments are blank separated added to the line, the last argument must be a NULL. */ gpg_error_t dirmngr_status (ctrl_t ctrl, const char *keyword, ...) { gpg_error_t err = 0; va_list arg_ptr; assuan_context_t ctx; va_start (arg_ptr, keyword); if (ctrl->server_local && (ctx = ctrl->server_local->assuan_ctx)) { err = vprint_assuan_status_strings (ctx, keyword, arg_ptr); } va_end (arg_ptr); return err; } /* Print a help status line. The function splits text at LFs. */ gpg_error_t dirmngr_status_help (ctrl_t ctrl, const char *text) { gpg_error_t err = 0; assuan_context_t ctx; if (ctrl->server_local && (ctx = ctrl->server_local->assuan_ctx)) { char buf[950], *p; size_t n; do { p = buf; n = 0; for ( ; *text && *text != '\n' && n < DIM (buf)-2; n++) *p++ = *text++; if (*text == '\n') text++; *p = 0; err = assuan_write_status (ctx, "#", buf); } while (!err && *text); } return err; } /* Print a help status line using a printf like format. The function * splits text at LFs. */ gpg_error_t dirmngr_status_helpf (ctrl_t ctrl, const char *format, ...) { va_list arg_ptr; gpg_error_t err; char *buf; va_start (arg_ptr, format); buf = es_vbsprintf (format, arg_ptr); err = buf? 0 : gpg_error_from_syserror (); va_end (arg_ptr); if (!err) err = dirmngr_status_help (ctrl, buf); es_free (buf); return err; } /* This function is similar to print_assuan_status but takes a CTRL * arg instead of an assuan context as first argument. */ gpg_error_t dirmngr_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx; if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx)) return 0; va_start (arg_ptr, format); err = vprint_assuan_status (ctx, keyword, format, arg_ptr); va_end (arg_ptr); return err; } /* Send a tick progress indicator back. Fixme: This is only done for the currently active channel. */ gpg_error_t dirmngr_tick (ctrl_t ctrl) { static time_t next_tick = 0; gpg_error_t err = 0; time_t now = time (NULL); if (!next_tick) { next_tick = now + 1; } else if ( now > next_tick ) { if (ctrl) { err = dirmngr_status (ctrl, "PROGRESS", "tick", "? 0 0", NULL); if (err) { /* Take this as in indication for a cancel request. */ err = gpg_error (GPG_ERR_CANCELED); } now = time (NULL); } next_tick = now + 1; } return err; } diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index a15ff240d..cc958ccf8 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -1,1095 +1,1137 @@ /* call-dirmngr.c - Communication with the dirmngr * Copyright (C) 2002, 2003, 2005, 2007, 2008, * 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 . */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "../common/i18n.h" #include "keydb.h" #include "../common/asshelp.h" struct membuf { size_t len; size_t size; char *buf; int out_of_core; }; /* fixme: We need a context for each thread or serialize the access to the dirmngr. */ static assuan_context_t dirmngr_ctx = NULL; static assuan_context_t dirmngr2_ctx = NULL; static int dirmngr_ctx_locked; static int dirmngr2_ctx_locked; struct inq_certificate_parm_s { ctrl_t ctrl; assuan_context_t ctx; ksba_cert_t cert; ksba_cert_t issuer_cert; }; struct isvalid_status_parm_s { ctrl_t ctrl; int seen; unsigned char fpr[20]; + gnupg_isotime_t revoked_at; + char *revocation_reason; /* malloced or NULL */ }; struct lookup_parm_s { ctrl_t ctrl; assuan_context_t ctx; void (*cb)(void *, ksba_cert_t); void *cb_value; struct membuf data; int error; }; struct run_command_parm_s { ctrl_t ctrl; assuan_context_t ctx; }; static gpg_error_t get_cached_cert (assuan_context_t ctx, const unsigned char *fpr, ksba_cert_t *r_cert); /* A simple implementation of a dynamic buffer. Use init_membuf() to create a buffer, put_membuf to append bytes and get_membuf to release and return the buffer. Allocation errors are detected but only returned at the final get_membuf(), this helps not to clutter the code with out of core checks. */ static void init_membuf (struct membuf *mb, int initiallen) { mb->len = 0; mb->size = initiallen; mb->out_of_core = 0; mb->buf = xtrymalloc (initiallen); if (!mb->buf) mb->out_of_core = 1; } static void put_membuf (struct membuf *mb, const void *buf, size_t len) { if (mb->out_of_core) return; if (mb->len + len >= mb->size) { char *p; mb->size += len + 1024; p = xtryrealloc (mb->buf, mb->size); if (!p) { mb->out_of_core = 1; return; } mb->buf = p; } memcpy (mb->buf + mb->len, buf, len); mb->len += len; } static void * get_membuf (struct membuf *mb, size_t *len) { char *p; if (mb->out_of_core) { xfree (mb->buf); mb->buf = NULL; return NULL; } p = mb->buf; *len = mb->len; mb->buf = NULL; mb->out_of_core = 1; /* don't allow a reuse */ return p; } /* Print a warning if the server's version number is less than our version number. Returns an error code on a connection problem. */ static gpg_error_t warn_version_mismatch (ctrl_t ctrl, assuan_context_t ctx, const char *servername, int mode) { return warn_server_version_mismatch (ctx, servername, mode, gpgsm_status2, ctrl, !opt.quiet); } /* This function prepares the dirmngr for a new session. The audit-events option is used so that other dirmngr clients won't get disturbed by such events. */ static void prepare_dirmngr (ctrl_t ctrl, assuan_context_t ctx, gpg_error_t err) { strlist_t server; if (!err) err = warn_version_mismatch (ctrl, ctx, DIRMNGR_NAME, 0); if (!err) { err = assuan_transact (ctx, "OPTION audit-events=1", NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* Allow the use of old dirmngr versions. */ } audit_log_ok (ctrl->audit, AUDIT_DIRMNGR_READY, err); if (!ctx || err) return; server = opt.keyserver; while (server) { char line[ASSUAN_LINELENGTH]; /* If the host is "ldap" we prefix the entire line with "ldap:" * to avoid an ambiguity on the server due to the introduction * of this optional prefix. */ snprintf (line, DIM (line), "LDAPSERVER %s%s", !strncmp (server->d, "ldap:", 5)? "ldap:":"", server->d); assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); /* The code below is not required because we don't return an error. */ /* err = [above call] */ /* if (gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD) */ /* err = 0; /\* Allow the use of old dirmngr versions. *\/ */ server = server->next; } } /* Return a new assuan context for a Dirmngr connection. */ static gpg_error_t start_dirmngr_ext (ctrl_t ctrl, assuan_context_t *ctx_r) { gpg_error_t err; assuan_context_t ctx; if (opt.disable_dirmngr || ctrl->offline) return gpg_error (GPG_ERR_NO_DIRMNGR); if (*ctx_r) return 0; /* Note: if you change this to multiple connections, you also need to take care of the implicit option sending caching. */ err = start_new_dirmngr (&ctx, GPG_ERR_SOURCE_DEFAULT, opt.dirmngr_program, opt.autostart, opt.verbose, DBG_IPC, gpgsm_status2, ctrl); if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR) { static int shown; if (!shown) { shown = 1; log_info (_("no dirmngr running in this session\n")); } } prepare_dirmngr (ctrl, ctx, err); if (err) return err; *ctx_r = ctx; return 0; } static int start_dirmngr (ctrl_t ctrl) { gpg_error_t err; log_assert (! dirmngr_ctx_locked); dirmngr_ctx_locked = 1; err = start_dirmngr_ext (ctrl, &dirmngr_ctx); /* We do not check ERR but the existence of a context because the error might come from a failed command send to the dirmngr. Fixme: Why don't we close the drimngr context if we encountered an error in prepare_dirmngr? */ if (!dirmngr_ctx) dirmngr_ctx_locked = 0; return err; } static void release_dirmngr (ctrl_t ctrl) { (void)ctrl; if (!dirmngr_ctx_locked) log_error ("WARNING: trying to release a non-locked dirmngr ctx\n"); dirmngr_ctx_locked = 0; } static int start_dirmngr2 (ctrl_t ctrl) { gpg_error_t err; log_assert (! dirmngr2_ctx_locked); dirmngr2_ctx_locked = 1; err = start_dirmngr_ext (ctrl, &dirmngr2_ctx); if (!dirmngr2_ctx) dirmngr2_ctx_locked = 0; return err; } static void release_dirmngr2 (ctrl_t ctrl) { (void)ctrl; if (!dirmngr2_ctx_locked) log_error ("WARNING: trying to release a non-locked dirmngr2 ctx\n"); dirmngr2_ctx_locked = 0; } /* Handle a SENDCERT inquiry. */ static gpg_error_t inq_certificate (void *opaque, const char *line) { struct inq_certificate_parm_s *parm = opaque; const char *s; int rc; size_t n; const unsigned char *der; size_t derlen; int issuer_mode = 0; ksba_sexp_t ski = NULL; if ((s = has_leading_keyword (line, "SENDCERT"))) { line = s; } else if ((s = has_leading_keyword (line, "SENDCERT_SKI"))) { /* Send a certificate where a sourceKeyIdentifier is included. */ line = s; ski = make_simple_sexp_from_hexstr (line, &n); line += n; while (*line == ' ') line++; } else if ((s = has_leading_keyword (line, "SENDISSUERCERT"))) { line = s; issuer_mode = 1; } else if ((s = has_leading_keyword (line, "ISTRUSTED"))) { /* The server is asking us whether the certificate is a trusted root certificate. */ char fpr[41]; struct rootca_flags_s rootca_flags; line = s; for (s=line,n=0; hexdigitp (s); s++, n++) ; if (*s || n != 40) return gpg_error (GPG_ERR_ASS_PARAMETER); for (s=line, n=0; n < 40; s++, n++) fpr[n] = (*s >= 'a')? (*s & 0xdf): *s; fpr[n] = 0; if (!gpgsm_agent_istrusted (parm->ctrl, NULL, fpr, &rootca_flags)) rc = assuan_send_data (parm->ctx, "1", 1); else rc = 0; return rc; } else { log_error ("unsupported inquiry '%s'\n", line); return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } if (!*line) { /* Send the current certificate. */ der = ksba_cert_get_image (issuer_mode? parm->issuer_cert : parm->cert, &derlen); if (!der) rc = gpg_error (GPG_ERR_INV_CERT_OBJ); else rc = assuan_send_data (parm->ctx, der, derlen); } else if (issuer_mode) { log_error ("sending specific issuer certificate back " "is not yet implemented\n"); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } else { /* Send the given certificate. */ int err; ksba_cert_t cert; err = gpgsm_find_cert (parm->ctrl, line, ski, &cert, 1); if (err) { log_error ("certificate not found: %s\n", gpg_strerror (err)); rc = gpg_error (GPG_ERR_NOT_FOUND); } else { der = ksba_cert_get_image (cert, &derlen); if (!der) rc = gpg_error (GPG_ERR_INV_CERT_OBJ); else rc = assuan_send_data (parm->ctx, der, derlen); ksba_cert_release (cert); } } xfree (ski); return rc; } /* Take a 20 byte hexencoded string and put it into the provided 20 byte buffer FPR in binary format. */ static int unhexify_fpr (const char *hexstr, unsigned char *fpr) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if (*s || (n != 40)) return 0; /* no fingerprint (invalid or wrong length). */ for (s=hexstr, n=0; *s; s += 2, n++) fpr[n] = xtoi_2 (s); return 1; /* okay */ } /* This is a helper to print diagnostics from dirmngr indicated by * WARNING or NOTE status lines. Returns true if the status LINE was * processed. */ static int warning_and_note_printer (const char *line) { const char *s, *s2; const char *warn = NULL; int is_note = 0; if ((s = has_leading_keyword (line, "WARNING"))) ; else if ((is_note = !!(s = has_leading_keyword (line, "NOTE")))) ; else return 0; /* Nothing to process. */ if ((s2 = has_leading_keyword (s, "no_crl_due_to_tor")) || (s2 = has_leading_keyword (s, "no_ldap_due_to_tor")) || (s2 = has_leading_keyword (s, "no_ocsp_due_to_tor"))) warn = _("Tor might be in use - network access is limited"); else warn = NULL; if (warn) { if (is_note) log_info (_("Note: %s\n"), warn); else log_info (_("WARNING: %s\n"), warn); if (s2) { while (*s2 && !spacep (s2)) s2++; while (*s2 && spacep (s2)) s2++; if (*s2) gpgsm_print_further_info ("%s", s2); } } return 1; /* Status line processed. */ } static gpg_error_t isvalid_status_cb (void *opaque, const char *line) { struct isvalid_status_parm_s *parm = opaque; const char *s; if ((s = has_leading_keyword (line, "PROGRESS"))) { if (parm->ctrl) { line = s; if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) return gpg_error (GPG_ERR_ASS_CANCELED); } } else if ((s = has_leading_keyword (line, "ONLY_VALID_IF_CERT_VALID"))) { parm->seen++; if (!*s || !unhexify_fpr (s, parm->fpr)) parm->seen++; /* Bump it to indicate an error. */ } + else if ((s = has_leading_keyword (line, "REVOCATIONINFO"))) + { + if (*s && strlen (s) >= 15) + { + memcpy (parm->revoked_at, s, 15); + parm->revoked_at[15] = 0; + } + s += 15; + while (*s && spacep (s)) + s++; + xfree (parm->revocation_reason); + parm->revocation_reason = *s? xtrystrdup (s) : NULL; + } else if (warning_and_note_printer (line)) { } return 0; } /* Call the directory manager to check whether the certificate is valid Returns 0 for valid or usually one of the errors: GPG_ERR_CERTIFICATE_REVOKED GPG_ERR_NO_CRL_KNOWN GPG_ERR_CRL_TOO_OLD Values for USE_OCSP: 0 = Do CRL check. 1 = Do an OCSP check but fallback to CRL unless CRLs are disabled. 2 = Do only an OCSP check (used for the chain model). + + If R_REVOKED_AT pr R_REASON are not NULL and the certificate has + been revoked the revocation time and the reason are copied to there. + The caller needs to free R_REASON. */ gpg_error_t gpgsm_dirmngr_isvalid (ctrl_t ctrl, - ksba_cert_t cert, ksba_cert_t issuer_cert, int use_ocsp) + ksba_cert_t cert, ksba_cert_t issuer_cert, int use_ocsp, + gnupg_isotime_t r_revoked_at, char **r_reason) { static int did_options; int rc; char *certid, *certfpr; char line[ASSUAN_LINELENGTH]; struct inq_certificate_parm_s parm; struct isvalid_status_parm_s stparm; + if (r_revoked_at) + *r_revoked_at = 0; + if (r_reason) + *r_reason = NULL; + rc = start_dirmngr (ctrl); if (rc) return rc; certfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); certid = gpgsm_get_certid (cert); if (!certid) { log_error ("error getting the certificate ID\n"); release_dirmngr (ctrl); return gpg_error (GPG_ERR_GENERAL); } if (opt.verbose > 1) { char *fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1); log_info ("asking dirmngr about %s%s\n", fpr, use_ocsp? " (using OCSP)":""); xfree (fpr); } parm.ctx = dirmngr_ctx; parm.ctrl = ctrl; parm.cert = cert; parm.issuer_cert = issuer_cert; stparm.ctrl = ctrl; stparm.seen = 0; memset (stparm.fpr, 0, 20); + stparm.revoked_at[0] = 0; + stparm.revocation_reason = NULL; /* It is sufficient to send the options only once because we have * one connection per process only. */ if (!did_options) { if (opt.force_crl_refresh) assuan_transact (dirmngr_ctx, "OPTION force-crl-refresh=1", NULL, NULL, NULL, NULL, NULL, NULL); did_options = 1; } snprintf (line, DIM(line), "ISVALID%s %s%s%s", (use_ocsp == 2 || opt.no_crl_check) ? " --only-ocsp":"", certid, use_ocsp? " ":"", use_ocsp? certfpr:""); xfree (certid); xfree (certfpr); rc = assuan_transact (dirmngr_ctx, line, NULL, NULL, inq_certificate, &parm, isvalid_status_cb, &stparm); if (opt.verbose > 1) log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay"); + if (gpg_err_code (rc) == GPG_ERR_CERT_REVOKED + && !check_isotime (stparm.revoked_at)) + { + if (r_revoked_at) + gnupg_copy_time (r_revoked_at, stparm.revoked_at); + if (r_reason) + { + *r_reason = stparm.revocation_reason; + stparm.revocation_reason = NULL; + } + + } + if (!rc && stparm.seen) { /* Need to also check the certificate validity. */ if (stparm.seen != 1) { log_error ("communication problem with dirmngr detected\n"); rc = gpg_error (GPG_ERR_INV_CRL); } else { ksba_cert_t rspcert = NULL; if (get_cached_cert (dirmngr_ctx, stparm.fpr, &rspcert)) { /* Ooops: Something went wrong getting the certificate from the dirmngr. Try our own cert store now. */ KEYDB_HANDLE kh; kh = keydb_new (ctrl); if (!kh) rc = gpg_error (GPG_ERR_ENOMEM); if (!rc) rc = keydb_search_fpr (ctrl, kh, stparm.fpr); if (!rc) rc = keydb_get_cert (kh, &rspcert); if (rc) { log_error ("unable to find the certificate used " "by the dirmngr: %s\n", gpg_strerror (rc)); rc = gpg_error (GPG_ERR_INV_CRL); } keydb_release (kh); } if (!rc) { rc = gpgsm_cert_use_ocsp_p (rspcert); if (rc) rc = gpg_error (GPG_ERR_INV_CRL); else { /* Note the no_dirmngr flag: This avoids checking this certificate over and over again. */ rc = gpgsm_validate_chain (ctrl, rspcert, GNUPG_ISOTIME_NONE, NULL, 0, NULL, VALIDATE_FLAG_NO_DIRMNGR, NULL); if (rc) { log_error ("invalid certificate used for CRL/OCSP: %s\n", gpg_strerror (rc)); rc = gpg_error (GPG_ERR_INV_CRL); } } } ksba_cert_release (rspcert); } } + release_dirmngr (ctrl); + xfree (stparm.revocation_reason); return rc; } /* Lookup helpers*/ static gpg_error_t lookup_cb (void *opaque, const void *buffer, size_t length) { struct lookup_parm_s *parm = opaque; size_t len; char *buf; ksba_cert_t cert; int rc; if (parm->error) return 0; if (buffer) { put_membuf (&parm->data, buffer, length); return 0; } /* END encountered - process what we have */ buf = get_membuf (&parm->data, &len); if (!buf) { parm->error = gpg_error (GPG_ERR_ENOMEM); return 0; } rc = ksba_cert_new (&cert); if (rc) { parm->error = rc; return 0; } rc = ksba_cert_init_from_mem (cert, buf, len); if (rc) { log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc)); } else { parm->cb (parm->cb_value, cert); } ksba_cert_release (cert); init_membuf (&parm->data, 4096); return 0; } /* Return a properly escaped pattern from NAMES. The only error return is NULL to indicate a malloc failure. */ static char * pattern_from_strlist (strlist_t names) { strlist_t sl; int n; const char *s; char *pattern, *p; for (n=0, sl=names; sl; sl = sl->next) { for (s=sl->d; *s; s++, n++) { if (*s == '%' || *s == ' ' || *s == '+') n += 2; } n++; } p = pattern = xtrymalloc (n+1); if (!pattern) return NULL; for (sl=names; sl; sl = sl->next) { for (s=sl->d; *s; s++) { switch (*s) { case '%': *p++ = '%'; *p++ = '2'; *p++ = '5'; break; case ' ': *p++ = '%'; *p++ = '2'; *p++ = '0'; break; case '+': *p++ = '%'; *p++ = '2'; *p++ = 'B'; break; default: *p++ = *s; break; } } *p++ = ' '; } if (p == pattern) *pattern = 0; /* is empty */ else p[-1] = '\0'; /* remove trailing blank */ return pattern; } static gpg_error_t lookup_status_cb (void *opaque, const char *line) { struct lookup_parm_s *parm = opaque; const char *s; if ((s = has_leading_keyword (line, "PROGRESS"))) { if (parm->ctrl) { line = s; if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line)) return gpg_error (GPG_ERR_ASS_CANCELED); } } else if ((s = has_leading_keyword (line, "TRUNCATED"))) { if (parm->ctrl) { line = s; gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line); } } else if (warning_and_note_printer (line)) { } return 0; } /* Run the Directory Manager's lookup command using the pattern compiled from the strings given in NAMES or from URI. The caller must provide the callback CB which will be passed cert by cert. Note that CTRL is optional. With CACHE_ONLY the dirmngr will search only its own key cache. */ int gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, const char *uri, int cache_only, void (*cb)(void*, ksba_cert_t), void *cb_value) { int rc; char line[ASSUAN_LINELENGTH]; struct lookup_parm_s parm; size_t len; assuan_context_t ctx; const char *s; if ((names && uri) || (!names && !uri)) return gpg_error (GPG_ERR_INV_ARG); /* The lookup function can be invoked from the callback of a lookup function, for example to walk the chain. */ if (!dirmngr_ctx_locked) { rc = start_dirmngr (ctrl); if (rc) return rc; ctx = dirmngr_ctx; } else if (!dirmngr2_ctx_locked) { rc = start_dirmngr2 (ctrl); if (rc) return rc; ctx = dirmngr2_ctx; } else { log_fatal ("both dirmngr contexts are in use\n"); } if (names) { char *pattern = pattern_from_strlist (names); if (!pattern) { if (ctx == dirmngr_ctx) release_dirmngr (ctrl); else release_dirmngr2 (ctrl); return out_of_core (); } snprintf (line, DIM(line), "LOOKUP%s %s", cache_only? " --cache-only":"", pattern); xfree (pattern); } else { for (s=uri; *s; s++) if (*s <= ' ') { if (ctx == dirmngr_ctx) release_dirmngr (ctrl); else release_dirmngr2 (ctrl); return gpg_error (GPG_ERR_INV_URI); } snprintf (line, DIM(line), "LOOKUP --url %s", uri); } parm.ctrl = ctrl; parm.ctx = ctx; parm.cb = cb; parm.cb_value = cb_value; parm.error = 0; init_membuf (&parm.data, 4096); rc = assuan_transact (ctx, line, lookup_cb, &parm, NULL, NULL, lookup_status_cb, &parm); xfree (get_membuf (&parm.data, &len)); if (ctx == dirmngr_ctx) release_dirmngr (ctrl); else release_dirmngr2 (ctrl); if (rc) return rc; return parm.error; } static gpg_error_t get_cached_cert_data_cb (void *opaque, const void *buffer, size_t length) { struct membuf *mb = opaque; if (buffer) put_membuf (mb, buffer, length); return 0; } /* Return a certificate from the Directory Manager's cache. This function only returns one certificate which must be specified using the fingerprint FPR and will be stored at R_CERT. On error NULL is stored at R_CERT and an error code returned. Note that the caller must provide the locked dirmngr context CTX. */ static gpg_error_t get_cached_cert (assuan_context_t ctx, const unsigned char *fpr, ksba_cert_t *r_cert) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; char hexfpr[2*20+1]; struct membuf mb; char *buf; size_t buflen = 0; ksba_cert_t cert; *r_cert = NULL; bin2hex (fpr, 20, hexfpr); snprintf (line, DIM(line), "LOOKUP --single --cache-only 0x%s", hexfpr); init_membuf (&mb, 4096); err = assuan_transact (ctx, line, get_cached_cert_data_cb, &mb, NULL, NULL, NULL, NULL); buf = get_membuf (&mb, &buflen); if (err) { xfree (buf); return err; } if (!buf) return gpg_error (GPG_ERR_ENOMEM); err = ksba_cert_new (&cert); if (err) { xfree (buf); return err; } err = ksba_cert_init_from_mem (cert, buf, buflen); xfree (buf); if (err) { log_error ("failed to parse a certificate: %s\n", gpg_strerror (err)); ksba_cert_release (cert); return err; } *r_cert = cert; return 0; } /* Run Command helpers*/ /* Fairly simple callback to write all output of dirmngr to stdout. */ static gpg_error_t run_command_cb (void *opaque, const void *buffer, size_t length) { (void)opaque; if (buffer) { if ( fwrite (buffer, length, 1, stdout) != 1 ) log_error ("error writing to stdout: %s\n", strerror (errno)); } return 0; } /* Handle inquiries from the dirmngr COMMAND. */ static gpg_error_t run_command_inq_cb (void *opaque, const char *line) { struct run_command_parm_s *parm = opaque; const char *s; int rc = 0; if ((s = has_leading_keyword (line, "SENDCERT"))) { /* send the given certificate */ int err; ksba_cert_t cert; const unsigned char *der; size_t derlen; line = s; if (!*line) return gpg_error (GPG_ERR_ASS_PARAMETER); err = gpgsm_find_cert (parm->ctrl, line, NULL, &cert, 1); if (err) { log_error ("certificate not found: %s\n", gpg_strerror (err)); rc = gpg_error (GPG_ERR_NOT_FOUND); } else { der = ksba_cert_get_image (cert, &derlen); if (!der) rc = gpg_error (GPG_ERR_INV_CERT_OBJ); else rc = assuan_send_data (parm->ctx, der, derlen); ksba_cert_release (cert); } } else if ((s = has_leading_keyword (line, "PRINTINFO"))) { /* Simply show the message given in the argument. */ line = s; log_info ("dirmngr: %s\n", line); } else { log_error ("unsupported inquiry '%s'\n", line); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } return rc; } static gpg_error_t run_command_status_cb (void *opaque, const char *line) { ctrl_t ctrl = opaque; const char *s; if (opt.verbose) { log_info ("dirmngr status: %s\n", line); } if ((s = has_leading_keyword (line, "PROGRESS"))) { if (ctrl) { line = s; if (gpgsm_status (ctrl, STATUS_PROGRESS, line)) return gpg_error (GPG_ERR_ASS_CANCELED); } } else if (warning_and_note_printer (line)) { } return 0; } /* Pass COMMAND to dirmngr and print all output generated by Dirmngr to stdout. A couple of inquiries are defined (see above). ARGC arguments in ARGV are given to the Dirmngr. Spaces, plus and percent characters within the argument strings are percent escaped so that blanks can act as delimiters. */ int gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command, int argc, char **argv) { int rc; int i; const char *s; char *line, *p; size_t len; struct run_command_parm_s parm; rc = start_dirmngr (ctrl); if (rc) return rc; parm.ctrl = ctrl; parm.ctx = dirmngr_ctx; len = strlen (command) + 1; for (i=0; i < argc; i++) len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */ line = xtrymalloc (len); if (!line) { release_dirmngr (ctrl); return out_of_core (); } p = stpcpy (line, command); for (i=0; i < argc; i++) { *p++ = ' '; for (s=argv[i]; *s; s++) { if (!isascii (*s)) *p++ = *s; else if (*s == ' ') *p++ = '+'; else if (!isprint (*s) || *s == '+') { sprintf (p, "%%%02X", *(const unsigned char *)s); p += 3; } else *p++ = *s; } } *p = 0; rc = assuan_transact (dirmngr_ctx, line, run_command_cb, NULL, run_command_inq_cb, &parm, run_command_status_cb, ctrl); xfree (line); log_info ("response of dirmngr: %s\n", rc? gpg_strerror (rc): "okay"); release_dirmngr (ctrl); return rc; } diff --git a/sm/certchain.c b/sm/certchain.c index efe8cb1d5..abce4c327 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -1,2402 +1,2417 @@ /* certchain.c - certificate chain validation * Copyright (C) 2001, 2002, 2003, 2004, 2005, * 2006, 2007, 2008, 2011 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 . */ #include #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ #include "../common/i18n.h" #include "../common/tlv.h" /* The OID for the authorityInfoAccess's caIssuers. */ static const char oidstr_caIssuers[] = "1.3.6.1.5.5.7.48.2"; /* Object to keep track of certain root certificates. */ struct marktrusted_info_s { struct marktrusted_info_s *next; unsigned char fpr[20]; }; static struct marktrusted_info_s *marktrusted_info; /* While running the validation function we want to keep track of the certificates in the chain. This type is used for that. */ struct chain_item_s { struct chain_item_s *next; ksba_cert_t cert; /* The certificate. */ int is_root; /* The certificate is the root certificate. */ }; typedef struct chain_item_s *chain_item_t; static int is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn); static int get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen); /* This function returns true if we already asked during this session whether the root certificate CERT shall be marked as trusted. */ static int already_asked_marktrusted (ksba_cert_t cert) { unsigned char fpr[20]; struct marktrusted_info_s *r; gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL); /* No context switches in the loop! */ for (r=marktrusted_info; r; r= r->next) if (!memcmp (r->fpr, fpr, 20)) return 1; return 0; } /* Flag certificate CERT as already asked whether it shall be marked as trusted. */ static void set_already_asked_marktrusted (ksba_cert_t cert) { unsigned char fpr[20]; struct marktrusted_info_s *r; gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL); for (r=marktrusted_info; r; r= r->next) if (!memcmp (r->fpr, fpr, 20)) return; /* Already marked. */ r = xtrycalloc (1, sizeof *r); if (!r) return; memcpy (r->fpr, fpr, 20); r->next = marktrusted_info; marktrusted_info = r; } /* If LISTMODE is true, print FORMAT using LISTMODE to FP. If LISTMODE is false, use the string to print an log_info or, if IS_ERROR is true, and log_error. */ static void do_list (int is_error, int listmode, estream_t fp, const char *format, ...) { va_list arg_ptr; va_start (arg_ptr, format) ; if (listmode) { if (fp) { es_fputs (" [", fp); es_vfprintf (fp, format, arg_ptr); es_fputs ("]\n", fp); } } else { log_logv (is_error? GPGRT_LOGLVL_ERROR: GPGRT_LOGLVL_INFO, format, arg_ptr); log_printf ("\n"); } va_end (arg_ptr); } /* Return 0 if A and B are equal. */ static int compare_certs (ksba_cert_t a, ksba_cert_t b) { const unsigned char *img_a, *img_b; size_t len_a, len_b; img_a = ksba_cert_get_image (a, &len_a); if (!img_a) return 1; img_b = ksba_cert_get_image (b, &len_b); if (!img_b) return 1; return !(len_a == len_b && !memcmp (img_a, img_b, len_a)); } /* Return true if CERT has the validityModel extensions and defines the use of the chain model. */ static int has_validation_model_chain (ksba_cert_t cert, int listmode, estream_t listfp) { gpg_error_t err; int idx, yes; const char *oid; size_t off, derlen, objlen, hdrlen; const unsigned char *der; int class, tag, constructed, ndef; char *oidbuf; for (idx=0; !(err=ksba_cert_get_extension (cert, idx, &oid, NULL, &off, &derlen));idx++) if (!strcmp (oid, "1.3.6.1.4.1.8301.3.5") ) break; if (err) return 0; /* Not found. */ der = ksba_cert_get_image (cert, NULL); if (!der) { err = gpg_error (GPG_ERR_INV_OBJ); /* Oops */ goto leave; } der += off; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; derlen = objlen; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_OBJECT_ID)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; oidbuf = ksba_oid_to_str (der, objlen); if (!oidbuf) { err = gpg_error_from_syserror (); goto leave; } if (opt.verbose) do_list (0, listmode, listfp, _("validation model requested by certificate: %s"), !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1")? _("chain") : !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.2")? _("shell") : /* */ oidbuf); yes = !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1"); ksba_free (oidbuf); return yes; leave: log_error ("error parsing validityModel: %s\n", gpg_strerror (err)); return 0; } static int unknown_criticals (ksba_cert_t cert, int listmode, estream_t fp) { static const char *known[] = { "2.5.29.15", /* keyUsage */ "2.5.29.17", /* subjectAltName Japanese DoCoMo certs mark them as critical. PKIX only requires them as critical if subjectName is empty. I don't know whether our code gracefully handles such empry subjectNames but that is another story. */ "2.5.29.19", /* basic Constraints */ "2.5.29.32", /* certificatePolicies */ "2.5.29.37", /* extendedKeyUsage - handled by certlist.c */ "1.3.6.1.4.1.8301.3.5", /* validityModel - handled here. */ NULL }; int rc = 0, i, idx, crit; const char *oid; gpg_error_t err; int unsupported; strlist_t sl; for (idx=0; !(err=ksba_cert_get_extension (cert, idx, &oid, &crit, NULL, NULL));idx++) { if (!crit) continue; for (i=0; known[i] && strcmp (known[i],oid); i++) ; unsupported = !known[i]; /* If this critical extension is not supported. Check the list of to be ignored extensions to see whether we claim that it is supported. */ if (unsupported && opt.ignored_cert_extensions) { for (sl=opt.ignored_cert_extensions; sl && strcmp (sl->d, oid); sl = sl->next) ; if (sl) unsupported = 0; } if (unsupported) { do_list (1, listmode, fp, _("critical certificate extension %s is not supported"), oid); rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT); } } /* We ignore the error codes EOF as well as no-value. The later will occur for certificates with no extensions at all. */ if (err && gpg_err_code (err) != GPG_ERR_EOF && gpg_err_code (err) != GPG_ERR_NO_VALUE) rc = err; return rc; } /* Check whether CERT is an allowed certificate. This requires that CERT matches all requirements for such a CA, i.e. the BasicConstraints extension. The function returns 0 on success and the allowed length of the chain at CHAINLEN. */ static int allowed_ca (ctrl_t ctrl, ksba_cert_t cert, int *chainlen, int listmode, estream_t fp) { gpg_error_t err; int flag; err = ksba_cert_is_ca (cert, &flag, chainlen); if (err) return err; if (!flag) { if (get_regtp_ca_info (ctrl, cert, chainlen)) { /* Note that dirmngr takes a different way to cope with such certs. */ return 0; /* RegTP issued certificate. */ } do_list (1, listmode, fp,_("issuer certificate is not marked as a CA")); return gpg_error (GPG_ERR_BAD_CA_CERT); } return 0; } static int check_cert_policy (ksba_cert_t cert, int listmode, estream_t fplist) { gpg_error_t err; char *policies; estream_t fp; int any_critical; err = ksba_cert_get_cert_policies (cert, &policies); if (gpg_err_code (err) == GPG_ERR_NO_DATA) return 0; /* No policy given. */ if (err) return err; /* STRING is a line delimited list of certificate policies as stored in the certificate. The line itself is colon delimited where the first field is the OID of the policy and the second field either N or C for normal or critical extension */ if (opt.verbose > 1 && !listmode) log_info ("certificate's policy list: %s\n", policies); /* The check is very minimal but won't give false positives */ any_critical = !!strstr (policies, ":C"); if (!opt.policy_file) { xfree (policies); if (any_critical) { do_list (1, listmode, fplist, _("critical marked policy without configured policies")); return gpg_error (GPG_ERR_NO_POLICY_MATCH); } return 0; } fp = es_fopen (opt.policy_file, "r"); if (!fp) { if (opt.verbose || errno != ENOENT) log_info (_("failed to open '%s': %s\n"), opt.policy_file, strerror (errno)); xfree (policies); /* With no critical policies this is only a warning */ if (!any_critical) { if (opt.verbose) do_list (0, listmode, fplist, _("Note: non-critical certificate policy not allowed")); return 0; } do_list (1, listmode, fplist, _("certificate policy not allowed")); return gpg_error (GPG_ERR_NO_POLICY_MATCH); } for (;;) { int c; char *p, line[256]; char *haystack, *allowed; /* read line */ do { if (!es_fgets (line, DIM(line)-1, fp) ) { gpg_error_t tmperr = gpg_error_from_syserror (); xfree (policies); if (es_feof (fp)) { es_fclose (fp); /* With no critical policies this is only a warning */ if (!any_critical) { if (opt.verbose) do_list (0, listmode, fplist, _("Note: non-critical certificate policy not allowed")); return 0; } do_list (1, listmode, fplist, _("certificate policy not allowed")); return gpg_error (GPG_ERR_NO_POLICY_MATCH); } es_fclose (fp); return tmperr; } if (!*line || line[strlen(line)-1] != '\n') { /* eat until end of line */ while ((c = es_getc (fp)) != EOF && c != '\n') ; es_fclose (fp); xfree (policies); return gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); } /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; } while (!*p || *p == '\n' || *p == '#'); /* Parse line. Note that the line has always a LF and spacep does not consider a LF a space. Thus strpbrk will always succeed. */ for (allowed=line; spacep (allowed); allowed++) ; p = strpbrk (allowed, " :\n"); if (!*p || p == allowed) { es_fclose (fp); xfree (policies); return gpg_error (GPG_ERR_CONFIGURATION); } *p = 0; /* strip the rest of the line */ /* See whether we find ALLOWED (which is an OID) in POLICIES */ for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1) { if ( !(p == policies || p[-1] == '\n') ) continue; /* Does not match the begin of a line. */ if (p[strlen (allowed)] != ':') continue; /* The length does not match. */ /* Yep - it does match so return okay. */ es_fclose (fp); xfree (policies); return 0; } } } /* Helper function for find_up. This resets the key handle and search for an issuer ISSUER with a subjectKeyIdentifier of KEYID. Returns 0 on success or GPG_ERR_NOT_FOUND when not found. */ static int find_up_search_by_keyid (ctrl_t ctrl, KEYDB_HANDLE kh, const char *issuer, ksba_sexp_t keyid) { int rc; ksba_cert_t cert = NULL; ksba_sexp_t subj = NULL; ksba_isotime_t not_before, not_after, last_not_before, ne_last_not_before; ksba_cert_t found_cert = NULL; ksba_cert_t ne_found_cert = NULL; keydb_search_reset (kh); while (!(rc = keydb_search_subject (ctrl, kh, issuer))) { ksba_cert_release (cert); cert = NULL; rc = keydb_get_cert (kh, &cert); if (rc) { log_error ("keydb_get_cert failed in %s: %s <%s>\n", __func__, gpg_strerror (rc), gpg_strsource (rc)); rc = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } xfree (subj); if (!ksba_cert_get_subj_key_id (cert, NULL, &subj)) { if (!cmp_simple_canon_sexp (keyid, subj)) { /* Found matching cert. */ rc = ksba_cert_get_validity (cert, 0, not_before); if (!rc) rc = ksba_cert_get_validity (cert, 1, not_after); if (rc) { log_error ("keydb_get_validity() failed: rc=%d\n", rc); rc = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } if (!found_cert || strcmp (last_not_before, not_before) < 0) { /* This certificate is the first one found or newer * than the previous one. This copes with * re-issuing CA certificates while keeping the same * key information. */ gnupg_copy_time (last_not_before, not_before); ksba_cert_release (found_cert); ksba_cert_ref ((found_cert = cert)); keydb_push_found_state (kh); } if (*not_after && strcmp (ctrl->current_time, not_after) > 0 ) ; /* CERT has expired - don't consider it. */ else if (!ne_found_cert || strcmp (ne_last_not_before, not_before) < 0) { /* This certificate is the first non-expired one * found or newer than the previous non-expired one. */ gnupg_copy_time (ne_last_not_before, not_before); ksba_cert_release (ne_found_cert); ksba_cert_ref ((ne_found_cert = cert)); } } } } if (!found_cert) goto leave; /* Take the last saved one. Note that push/pop_found_state are * misnomers because there is no stack of states. Renaming them to * save/restore_found_state would be better. */ keydb_pop_found_state (kh); rc = 0; /* Ignore EOF or other error after the first cert. */ /* We need to consider some corner cases. It is possible that we * have a long term certificate (e.g. valid from 2008 to 2033) as * well as a re-issued (i.e. using the same key material) short term * certificate (say from 2016 to 2019). Using the short term * certificate is the proper solution. But we need to take care if * there is no re-issued new short term certificate (e.g. from 2020 * to 2023) available. In that case it is better to use the long * term certificate which is still valid. The code may run into * minor problems in the case of the chain validation mode. Given * that this corner case is due to non-diligent PKI management we * ignore this problem. */ /* The most common case is that the found certificate is not expired * and thus identical to the one found from the list of non-expired * certs. We can stop here. */ if (found_cert == ne_found_cert) goto leave; /* If we do not have a non expired certificate the actual cert is * expired and we can also stop here. */ if (!ne_found_cert) goto leave; /* Now we need to see whether the found certificate is expired and * only in this case we return the certificate found in the list of * non-expired certs. */ rc = ksba_cert_get_validity (found_cert, 1, not_after); if (rc) { log_error ("keydb_get_validity() failed: rc=%d\n", rc); rc = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } if (*not_after && strcmp (ctrl->current_time, not_after) > 0 ) { /* CERT has expired. Use the NE_FOUND_CERT. Because we have no * found state for this we need to search for it again. */ unsigned char fpr[20]; gpgsm_get_fingerprint (ne_found_cert, GCRY_MD_SHA1, fpr, NULL); keydb_search_reset (kh); rc = keydb_search_fpr (ctrl, kh, fpr); if (rc) { log_error ("keydb_search_fpr() failed: rc=%d\n", rc); rc = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } /* Ready. The NE_FOUND_CERT is available via keydb_get_cert. */ } leave: ksba_cert_release (found_cert); ksba_cert_release (ne_found_cert); ksba_cert_release (cert); xfree (subj); return rc? gpg_error (GPG_ERR_NOT_FOUND) : 0; } struct find_up_store_certs_s { ctrl_t ctrl; int count; unsigned int want_fpr:1; unsigned int got_fpr:1; unsigned char fpr[20]; }; static void find_up_store_certs_cb (void *cb_value, ksba_cert_t cert) { struct find_up_store_certs_s *parm = cb_value; if (keydb_store_cert (parm->ctrl, cert, 1, NULL)) log_error ("error storing issuer certificate as ephemeral\n"); else if (parm->want_fpr && !parm->got_fpr) { if (!gpgsm_get_fingerprint (cert, 0, parm->fpr, NULL)) log_error (_("failed to get the fingerprint\n")); else parm->got_fpr = 1; } parm->count++; } /* Helper for find_up(). Locate the certificate for ISSUER using an external lookup. KH is the keydb context we are currently using. On success 0 is returned and the certificate may be retrieved from the keydb using keydb_get_cert(). KEYID is the keyIdentifier from the AKI or NULL. */ static int find_up_external (ctrl_t ctrl, KEYDB_HANDLE kh, const char *issuer, ksba_sexp_t keyid) { int rc; strlist_t names = NULL; struct find_up_store_certs_s find_up_store_certs_parm; char *pattern; const char *s; find_up_store_certs_parm.ctrl = ctrl; find_up_store_certs_parm.want_fpr = 0; find_up_store_certs_parm.got_fpr = 0; find_up_store_certs_parm.count = 0; if (opt.verbose) log_info (_("looking up issuer at external location\n")); /* The Dirmngr process is confused about unknown attributes. As a quick and ugly hack we locate the CN and use the issuer string starting at this attribite. Fixme: we should have far better parsing for external lookups in the Dirmngr. */ s = strstr (issuer, "CN="); if (!s || s == issuer || s[-1] != ',') s = issuer; pattern = xtrymalloc (strlen (s)+2); if (!pattern) return gpg_error_from_syserror (); strcpy (stpcpy (pattern, "/"), s); add_to_strlist (&names, pattern); xfree (pattern); rc = gpgsm_dirmngr_lookup (ctrl, names, NULL, 0, find_up_store_certs_cb, &find_up_store_certs_parm); free_strlist (names); if (opt.verbose) log_info (_("number of issuers matching: %d\n"), find_up_store_certs_parm.count); if (rc) { log_error ("external key lookup failed: %s\n", gpg_strerror (rc)); rc = gpg_error (GPG_ERR_NOT_FOUND); } else if (!find_up_store_certs_parm.count) rc = gpg_err_code (rc) == GPG_ERR_NOT_FOUND; else { int old; /* The issuers are currently stored in the ephemeral key DB, so we temporary switch to ephemeral mode. */ old = keydb_set_ephemeral (kh, 1); if (keyid) rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid); else { keydb_search_reset (kh); rc = keydb_search_subject (ctrl, kh, issuer); } keydb_set_ephemeral (kh, old); } return rc; } /* Helper for find_up(). Locate the certificate for CERT using the * caIssuer from the authorityInfoAccess. KH is the keydb context we * are currently using. On success 0 is returned and the certificate * may be retrieved from the keydb using keydb_get_cert(). If no * suitable authorityInfoAccess is encoded in the certificate * GPG_ERR_NOT_FOUND is returned. */ static gpg_error_t find_up_via_auth_info_access (ctrl_t ctrl, KEYDB_HANDLE kh, ksba_cert_t cert) { gpg_error_t err; struct find_up_store_certs_s find_up_store_certs_parm; char *url, *ldapurl; int idx, i; char *oid; ksba_name_t name; find_up_store_certs_parm.ctrl = ctrl; find_up_store_certs_parm.want_fpr = 1; find_up_store_certs_parm.got_fpr = 0; find_up_store_certs_parm.count = 0; /* Find suitable URLs; if there is a http scheme we prefer that. */ url = ldapurl = NULL; for (idx=0; !url && !(err = ksba_cert_get_authority_info_access (cert, idx, &oid, &name)); idx++) { if (!strcmp (oid, oidstr_caIssuers)) { for (i=0; !url && ksba_name_enum (name, i); i++) { char *p = ksba_name_get_uri (name, i); if (p) { if (!strncmp (p, "http:", 5) || !strncmp (p, "https:", 6)) url = p; else if (ldapurl) xfree (p); /* We already got one. */ else if (!strncmp (p, "ldap:",5) || !strncmp (p, "ldaps:",6)) ldapurl = p; } else xfree (p); } } ksba_name_release (name); ksba_free (oid); } if (err && gpg_err_code (err) != GPG_ERR_EOF) { log_error (_("can't get authorityInfoAccess: %s\n"), gpg_strerror (err)); return err; } if (!url && ldapurl) { /* No HTTP scheme; fallback to LDAP if available. */ url = ldapurl; ldapurl = NULL; } xfree (ldapurl); if (!url) return gpg_error (GPG_ERR_NOT_FOUND); if (opt.verbose) log_info ("looking up issuer via authorityInfoAccess.caIssuers\n"); err = gpgsm_dirmngr_lookup (ctrl, NULL, url, 0, find_up_store_certs_cb, &find_up_store_certs_parm); /* Although we might receive several certificates we use only the * first one. Or more exacty the first one for which we retrieved * the fingerprint. */ if (opt.verbose) log_info ("number of caIssuers found: %d\n", find_up_store_certs_parm.count); if (err) { log_error ("external URL lookup failed: %s\n", gpg_strerror (err)); err = gpg_error (GPG_ERR_NOT_FOUND); } else if (!find_up_store_certs_parm.got_fpr) err = gpg_error (GPG_ERR_NOT_FOUND); else { int old; /* The retrieved certificates are currently stored in the * ephemeral key DB, so we temporary switch to ephemeral * mode. */ old = keydb_set_ephemeral (kh, 1); keydb_search_reset (kh); err = keydb_search_fpr (ctrl, kh, find_up_store_certs_parm.fpr); keydb_set_ephemeral (kh, old); } return err; } /* Helper for find_up(). Ask the dirmngr for the certificate for ISSUER with optional SERIALNO. KH is the keydb context we are currently using. With SUBJECT_MODE set, ISSUER is searched as the subject. On success 0 is returned and the certificate is available in the ephemeral DB. */ static int find_up_dirmngr (ctrl_t ctrl, KEYDB_HANDLE kh, ksba_sexp_t serialno, const char *issuer, int subject_mode) { int rc; strlist_t names = NULL; struct find_up_store_certs_s find_up_store_certs_parm; char *pattern; (void)kh; find_up_store_certs_parm.ctrl = ctrl; find_up_store_certs_parm.count = 0; if (opt.verbose) log_info (_("looking up issuer from the Dirmngr cache\n")); if (subject_mode) { pattern = xtrymalloc (strlen (issuer)+2); if (pattern) strcpy (stpcpy (pattern, "/"), issuer); } else if (serialno) pattern = gpgsm_format_sn_issuer (serialno, issuer); else { pattern = xtrymalloc (strlen (issuer)+3); if (pattern) strcpy (stpcpy (pattern, "#/"), issuer); } if (!pattern) return gpg_error_from_syserror (); add_to_strlist (&names, pattern); xfree (pattern); rc = gpgsm_dirmngr_lookup (ctrl, names, NULL, 1, find_up_store_certs_cb, &find_up_store_certs_parm); free_strlist (names); if (opt.verbose) log_info (_("number of matching certificates: %d\n"), find_up_store_certs_parm.count); if (rc && opt.verbose) log_info (_("dirmngr cache-only key lookup failed: %s\n"), gpg_strerror (rc)); return ((!rc && find_up_store_certs_parm.count) ? 0 : gpg_error (GPG_ERR_NOT_FOUND)); } /* Locate issuing certificate for CERT. ISSUER is the name of the issuer used as a fallback if the other methods don't work. If FIND_NEXT is true, the function shall return the next possible issuer. The certificate itself is not directly returned but a keydb_get_cert on the keydb context KH will return it. Returns 0 on success, GPG_ERR_NOT_FOUND if not found or another error code. */ static gpg_error_t find_up (ctrl_t ctrl, KEYDB_HANDLE kh, ksba_cert_t cert, const char *issuer, int find_next) { ksba_name_t authid; ksba_sexp_t authidno; ksba_sexp_t keyid; gpg_error_t err = gpg_error (GPG_ERR_NOT_FOUND); if (DBG_X509) log_debug ("looking for parent certificate\n"); if (!ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno)) { const char *s = ksba_name_enum (authid, 0); if (s && *authidno) { err = keydb_search_issuer_sn (ctrl, kh, s, authidno); if (err) keydb_search_reset (kh); if (!err && DBG_X509) log_debug (" found via authid and sn+issuer\n"); /* In case of an error, try to get the certificate from the dirmngr. That is done by trying to put that certificate into the ephemeral DB and let the code below do the actual retrieve. Thus there is no error checking. Skipped in find_next mode as usual. */ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next) find_up_dirmngr (ctrl, kh, authidno, s, 0); /* In case of an error try the ephemeral DB. We can't do that in find_next mode because we can't keep the search state then. */ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next) { int old = keydb_set_ephemeral (kh, 1); if (!old) { err = keydb_search_issuer_sn (ctrl, kh, s, authidno); if (err) keydb_search_reset (kh); if (!err && DBG_X509) log_debug (" found via authid and sn+issuer (ephem)\n"); } keydb_set_ephemeral (kh, old); } if (err) /* Need to make sure to have this error code. */ err = gpg_error (GPG_ERR_NOT_FOUND); } if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && keyid && !find_next) { /* Not found by AKI.issuer_sn. Lets try the AKI.ki instead. Loop over all certificates with that issuer as subject and stop for the one with a matching subjectKeyIdentifier. */ /* Fixme: Should we also search in the dirmngr? */ err = find_up_search_by_keyid (ctrl, kh, issuer, keyid); if (!err && DBG_X509) log_debug (" found via authid and keyid\n"); if (err) { int old = keydb_set_ephemeral (kh, 1); if (!old) err = find_up_search_by_keyid (ctrl, kh, issuer, keyid); if (!err && DBG_X509) log_debug (" found via authid and keyid (ephem)\n"); keydb_set_ephemeral (kh, old); } if (err) /* Need to make sure to have this error code. */ err = gpg_error (GPG_ERR_NOT_FOUND); } /* If we still didn't found it, try to find it via the subject from the dirmngr-cache. */ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next) { if (!find_up_dirmngr (ctrl, kh, NULL, issuer, 1)) { int old = keydb_set_ephemeral (kh, 1); if (keyid) err = find_up_search_by_keyid (ctrl, kh, issuer, keyid); else { keydb_search_reset (kh); err = keydb_search_subject (ctrl, kh, issuer); } keydb_set_ephemeral (kh, old); } if (err) /* Need to make sure to have this error code. */ err = gpg_error (GPG_ERR_NOT_FOUND); if (!err && DBG_X509) log_debug (" found via authid and issuer from dirmngr cache\n"); } /* If we still didn't found it, try an external lookup. */ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next && !ctrl->offline) { /* We allow AIA also if CRLs are enabled; both can be used * as a web bug so it does not make sense to not use AIA if * CRL checks are enabled. */ if ((opt.auto_issuer_key_retrieve || !opt.no_crl_check) && !find_up_via_auth_info_access (ctrl, kh, cert)) { if (DBG_X509) log_debug (" found via authorityInfoAccess.caIssuers\n"); err = 0; } else if (opt.auto_issuer_key_retrieve) { err = find_up_external (ctrl, kh, issuer, keyid); if (!err && DBG_X509) log_debug (" found via authid and external lookup\n"); } } /* Print a note so that the user does not feel too helpless when an issuer certificate was found and gpgsm prints BAD signature because it is not the correct one. */ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && opt.quiet) ; else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { if (!opt.quiet) { log_info ("%sissuer certificate ", find_next?"next ":""); if (keyid) { log_printf ("{"); gpgsm_dump_serial (keyid); log_printf ("} "); } if (authidno) { log_printf ("(#"); gpgsm_dump_serial (authidno); log_printf ("/"); gpgsm_dump_string (s); log_printf (") "); } log_printf ("not found using authorityKeyIdentifier\n"); } } else if (err) log_error ("failed to find authorityKeyIdentifier: err=%d\n", err); xfree (keyid); ksba_name_release (authid); xfree (authidno); } if (err) /* Not found via authorithyKeyIdentifier, try regular issuer name. */ err = keydb_search_subject (ctrl, kh, issuer); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next) { int old; /* Also try to get it from the Dirmngr cache. The function merely puts it into the ephemeral database. */ find_up_dirmngr (ctrl, kh, NULL, issuer, 0); /* Not found, let us see whether we have one in the ephemeral key DB. */ old = keydb_set_ephemeral (kh, 1); if (!old) { keydb_search_reset (kh); err = keydb_search_subject (ctrl, kh, issuer); } keydb_set_ephemeral (kh, old); if (!err && DBG_X509) log_debug (" found via issuer\n"); } /* Still not found. If enabled, try an external lookup. */ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next && !ctrl->offline) { if ((opt.auto_issuer_key_retrieve || !opt.no_crl_check) && !find_up_via_auth_info_access (ctrl, kh, cert)) { if (DBG_X509) log_debug (" found via authorityInfoAccess.caIssuers\n"); err = 0; } else if (opt.auto_issuer_key_retrieve) { err = find_up_external (ctrl, kh, issuer, NULL); if (!err && DBG_X509) log_debug (" found via issuer and external lookup\n"); } } return err; } /* Return the next certificate up in the chain starting at START. Returns GPG_ERR_NOT_FOUND when there are no more certificates. */ gpg_error_t gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next) { gpg_error_t err = 0; char *issuer = NULL; char *subject = NULL; KEYDB_HANDLE kh = keydb_new (ctrl); *r_next = NULL; if (!kh) { log_error (_("failed to allocate keyDB handle\n")); err = gpg_error (GPG_ERR_GENERAL); goto leave; } issuer = ksba_cert_get_issuer (start, 0); subject = ksba_cert_get_subject (start, 0); if (!issuer) { log_error ("no issuer found in certificate\n"); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } if (!subject) { log_error ("no subject found in certificate\n"); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } if (is_root_cert (start, issuer, subject)) { err = gpg_error (GPG_ERR_NOT_FOUND); /* we are at the root */ goto leave; } err = find_up (ctrl, kh, start, issuer, 0); if (err) { /* It is quite common not to have a certificate, so better don't print an error here. */ if (gpg_err_code (err) != GPG_ERR_NOT_FOUND && opt.verbose > 1) log_error ("failed to find issuer's certificate: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); err = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); goto leave; } err = keydb_get_cert (kh, r_next); if (err) { log_error ("keydb_get_cert failed in %s: %s <%s>\n", __func__, gpg_strerror (err), gpg_strsource (err)); err = gpg_error (GPG_ERR_GENERAL); } leave: xfree (issuer); xfree (subject); keydb_release (kh); return err; } /* Helper for gpgsm_is_root_cert. This one is used if the subject and issuer DNs are already known. */ static int is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn) { gpg_error_t err; int result = 0; ksba_sexp_t serialno; ksba_sexp_t ak_keyid; ksba_name_t ak_name; ksba_sexp_t ak_sn; const char *ak_name_str; ksba_sexp_t subj_keyid = NULL; if (!issuerdn || !subjectdn) return 0; /* No. */ if (strcmp (issuerdn, subjectdn)) return 0; /* No. */ err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn); if (err) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) return 1; /* Yes. Without a authorityKeyIdentifier this needs to be the Root certificate (our trust anchor). */ log_error ("error getting authorityKeyIdentifier: %s\n", gpg_strerror (err)); return 0; /* Well, it is broken anyway. Return No. */ } serialno = ksba_cert_get_serial (cert); if (!serialno) { log_error ("error getting serialno: %s\n", gpg_strerror (err)); goto leave; } /* Check whether the auth name's matches the issuer name+sn. If that is the case this is a root certificate. */ ak_name_str = ksba_name_enum (ak_name, 0); if (ak_name_str && !strcmp (ak_name_str, issuerdn) && !cmp_simple_canon_sexp (ak_sn, serialno)) { result = 1; /* Right, CERT is self-signed. */ goto leave; } /* Similar for the ak_keyid. */ if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid) && !cmp_simple_canon_sexp (ak_keyid, subj_keyid)) { result = 1; /* Right, CERT is self-signed. */ goto leave; } leave: ksba_free (subj_keyid); ksba_free (ak_keyid); ksba_name_release (ak_name); ksba_free (ak_sn); ksba_free (serialno); return result; } /* Check whether the CERT is a root certificate. Returns True if this is the case. */ int gpgsm_is_root_cert (ksba_cert_t cert) { char *issuer; char *subject; int yes; issuer = ksba_cert_get_issuer (cert, 0); subject = ksba_cert_get_subject (cert, 0); yes = is_root_cert (cert, issuer, subject); xfree (issuer); xfree (subject); return yes; } /* This is a helper for gpgsm_validate_chain. */ static gpg_error_t is_cert_still_valid (ctrl_t ctrl, int chain_model, int lm, estream_t fp, ksba_cert_t subject_cert, ksba_cert_t issuer_cert, int *any_revoked, int *any_no_crl, int *any_crl_too_old) { gpg_error_t err; + gnupg_isotime_t revoked_at; + char *reason; if (ctrl->offline || (opt.no_crl_check && !ctrl->use_ocsp)) { audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, gpg_error (GPG_ERR_NOT_ENABLED)); return 0; } if (!(chain_model || ctrl->use_ocsp) && !opt.enable_issuer_based_crl_check) { err = ksba_cert_get_crl_dist_point (subject_cert, 0, NULL, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_EOF) { /* No DP specified in the certificate. Thus the CA does not * consider a CRL useful and the user of the certificate * also does not consider this to be a critical thing. In * this case we can conclude that the certificate shall not * be revocable. Note that we reach this point here only if * no OCSP responder shall be used. */ audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, gpg_error (GPG_ERR_TRUE)); return 0; } } err = gpgsm_dirmngr_isvalid (ctrl, subject_cert, issuer_cert, - chain_model? 2 : !!ctrl->use_ocsp); + chain_model? 2 : !!ctrl->use_ocsp, + revoked_at, &reason); + if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED) + { + gnupg_copy_time (ctrl->revoked_at, revoked_at); + xfree (ctrl->revocation_reason); + ctrl->revocation_reason = reason; + reason = NULL; + } + else + { + xfree (reason); + reason = (NULL); + } audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, err); if (err) { if (!lm) gpgsm_cert_log_name (NULL, subject_cert); switch (gpg_err_code (err)) { case GPG_ERR_CERT_REVOKED: do_list (1, lm, fp, _("certificate has been revoked")); *any_revoked = 1; /* Store that in the keybox so that key listings are able to return the revoked flag. We don't care about error, though. */ keydb_set_cert_flags (ctrl, subject_cert, 1, KEYBOX_FLAG_VALIDITY, 0, ~0, VALIDITY_REVOKED); break; case GPG_ERR_NO_CRL_KNOWN: do_list (1, lm, fp, _("no CRL found for certificate")); *any_no_crl = 1; break; case GPG_ERR_NO_DATA: do_list (1, lm, fp, _("the status of the certificate is unknown")); *any_no_crl = 1; break; case GPG_ERR_CRL_TOO_OLD: do_list (1, lm, fp, _("the available CRL is too old")); if (!lm) log_info (_("please make sure that the " "\"dirmngr\" is properly installed\n")); *any_crl_too_old = 1; break; default: do_list (1, lm, fp, _("checking the CRL failed: %s"), gpg_strerror (err)); return err; } } return 0; } /* Helper for gpgsm_validate_chain to check the validity period of SUBJECT_CERT. The caller needs to pass EXPTIME which will be updated to the nearest expiration time seen. A DEPTH of 0 indicates the target certificate, -1 the final root certificate and other values intermediate certificates. */ static gpg_error_t check_validity_period (ksba_isotime_t current_time, ksba_cert_t subject_cert, ksba_isotime_t exptime, int listmode, estream_t listfp, int depth) { gpg_error_t err; ksba_isotime_t not_before, not_after; err = ksba_cert_get_validity (subject_cert, 0, not_before); if (!err) err = ksba_cert_get_validity (subject_cert, 1, not_after); if (err) { do_list (1, listmode, listfp, _("certificate with invalid validity: %s"), gpg_strerror (err)); return gpg_error (GPG_ERR_BAD_CERT); } if (*not_after) { if (!*exptime) gnupg_copy_time (exptime, not_after); else if (strcmp (not_after, exptime) < 0 ) gnupg_copy_time (exptime, not_after); } if (*not_before && strcmp (current_time, not_before) < 0 ) { do_list (1, listmode, listfp, depth == 0 ? _("certificate not yet valid") : depth == -1 ? _("root certificate not yet valid") : /* other */ _("intermediate certificate not yet valid")); if (!listmode) { log_info (" (valid from "); dump_isotime (not_before); log_printf (")\n"); } return gpg_error (GPG_ERR_CERT_TOO_YOUNG); } if (*not_after && strcmp (current_time, not_after) > 0 ) { do_list (opt.ignore_expiration?0:1, listmode, listfp, depth == 0 ? _("certificate has expired") : depth == -1 ? _("root certificate has expired") : /* other */ _("intermediate certificate has expired")); if (!listmode) { log_info (" (expired at "); dump_isotime (not_after); log_printf (")\n"); } if (opt.ignore_expiration) log_info ("WARNING: ignoring expiration\n"); else return gpg_error (GPG_ERR_CERT_EXPIRED); } return 0; } /* This is a variant of check_validity_period used with the chain model. The extra constraint here is that notBefore and notAfter must exists and if the additional argument CHECK_TIME is given this time is used to check the validity period of SUBJECT_CERT. */ static gpg_error_t check_validity_period_cm (ksba_isotime_t current_time, ksba_isotime_t check_time, ksba_cert_t subject_cert, ksba_isotime_t exptime, int listmode, estream_t listfp, int depth) { gpg_error_t err; ksba_isotime_t not_before, not_after; err = ksba_cert_get_validity (subject_cert, 0, not_before); if (!err) err = ksba_cert_get_validity (subject_cert, 1, not_after); if (err) { do_list (1, listmode, listfp, _("certificate with invalid validity: %s"), gpg_strerror (err)); return gpg_error (GPG_ERR_BAD_CERT); } if (!*not_before || !*not_after) { do_list (1, listmode, listfp, _("required certificate attributes missing: %s%s%s"), !*not_before? "notBefore":"", (!*not_before && !*not_after)? ", ":"", !*not_before? "notAfter":""); return gpg_error (GPG_ERR_BAD_CERT); } if (strcmp (not_before, not_after) > 0 ) { do_list (1, listmode, listfp, _("certificate with invalid validity")); log_info (" (valid from "); dump_isotime (not_before); log_printf (" expired at "); dump_isotime (not_after); log_printf (")\n"); return gpg_error (GPG_ERR_BAD_CERT); } if (!*exptime) gnupg_copy_time (exptime, not_after); else if (strcmp (not_after, exptime) < 0 ) gnupg_copy_time (exptime, not_after); if (strcmp (current_time, not_before) < 0 ) { do_list (1, listmode, listfp, depth == 0 ? _("certificate not yet valid") : depth == -1 ? _("root certificate not yet valid") : /* other */ _("intermediate certificate not yet valid")); if (!listmode) { log_info (" (valid from "); dump_isotime (not_before); log_printf (")\n"); } return gpg_error (GPG_ERR_CERT_TOO_YOUNG); } if (*check_time && (strcmp (check_time, not_before) < 0 || strcmp (check_time, not_after) > 0)) { /* Note that we don't need a case for the root certificate because its own consistency has already been checked. */ do_list(opt.ignore_expiration?0:1, listmode, listfp, depth == 0 ? _("signature not created during lifetime of certificate") : depth == 1 ? _("certificate not created during lifetime of issuer") : _("intermediate certificate not created during lifetime " "of issuer")); if (!listmode) { log_info (depth== 0? _(" ( signature created at ") : /* */ _(" (certificate created at ") ); dump_isotime (check_time); log_printf (")\n"); log_info (depth==0? _(" (certificate valid from ") : /* */ _(" ( issuer valid from ") ); dump_isotime (not_before); log_info (" to "); dump_isotime (not_after); log_printf (")\n"); } if (opt.ignore_expiration) log_info ("WARNING: ignoring expiration\n"); else return gpg_error (GPG_ERR_CERT_EXPIRED); } return 0; } /* Ask the user whether he wants to mark the certificate CERT trusted. Returns true if the CERT is the trusted. We also check whether the agent is at all enabled to allow marktrusted and don't call it in this session again if it is not. */ static int ask_marktrusted (ctrl_t ctrl, ksba_cert_t cert, int listmode) { static int no_more_questions; int rc; char *fpr; int success = 0; fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1); log_info (_("fingerprint=%s\n"), fpr? fpr : "?"); xfree (fpr); if (no_more_questions) rc = gpg_error (GPG_ERR_NOT_SUPPORTED); else rc = gpgsm_agent_marktrusted (ctrl, cert); if (!rc) { log_info (_("root certificate has now been marked as trusted\n")); success = 1; } else if (!listmode) { gpgsm_dump_cert ("issuer", cert); log_info ("after checking the fingerprint, you may want " "to add it manually to the list of trusted certificates.\n"); } if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED) { if (!no_more_questions) log_info (_("interactive marking as trusted " "not enabled in gpg-agent\n")); no_more_questions = 1; } else if (gpg_err_code (rc) == GPG_ERR_CANCELED) { log_info (_("interactive marking as trusted " "disabled for this session\n")); no_more_questions = 1; } else set_already_asked_marktrusted (cert); return success; } /* Validate a chain and optionally return the nearest expiration time in R_EXPTIME. With LISTMODE set to 1 a special listmode is activated where only information about the certificate is printed to LISTFP and no output is send to the usual log stream. If CHECKTIME_ARG is set, it is used only in the chain model instead of the current time. Defined flag bits VALIDATE_FLAG_NO_DIRMNGR - Do not do any dirmngr isvalid checks. VALIDATE_FLAG_CHAIN_MODEL - Check according to chain model. VALIDATE_FLAG_STEED - Check according to the STEED model. */ static int do_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime_arg, ksba_isotime_t r_exptime, int listmode, estream_t listfp, unsigned int flags, struct rootca_flags_s *rootca_flags) { int rc = 0, depth, maxdepth; char *issuer = NULL; char *subject = NULL; KEYDB_HANDLE kh = NULL; ksba_cert_t subject_cert = NULL, issuer_cert = NULL; ksba_isotime_t current_time; ksba_isotime_t check_time; ksba_isotime_t exptime; int any_expired = 0; int any_revoked = 0; int any_no_crl = 0; int any_crl_too_old = 0; int any_no_policy_match = 0; int is_qualified = -1; /* Indicates whether the certificate stems from a qualified root certificate. -1 = unknown, 0 = no, 1 = yes. */ chain_item_t chain = NULL; /* A list of all certificates in the chain. */ gnupg_get_isotime (current_time); gnupg_copy_time (ctrl->current_time, current_time); if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) ) { if (!strcmp (checktime_arg, "19700101T000000")) { do_list (1, listmode, listfp, _("WARNING: creation time of signature not known - " "assuming current time")); gnupg_copy_time (check_time, current_time); } else gnupg_copy_time (check_time, checktime_arg); } else *check_time = 0; if (r_exptime) *r_exptime = 0; *exptime = 0; if (opt.no_chain_validation && !listmode) { log_info ("WARNING: bypassing certificate chain validation\n"); return 0; } kh = keydb_new (ctrl); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } if (DBG_X509 && !listmode) gpgsm_dump_cert ("target", cert); subject_cert = cert; ksba_cert_ref (subject_cert); maxdepth = 50; depth = 0; for (;;) { int is_root; gpg_error_t istrusted_rc = gpg_error (GPG_ERR_NOT_TRUSTED); /* Put the certificate on our list. */ { chain_item_t ci; ci = xtrycalloc (1, sizeof *ci); if (!ci) { rc = gpg_error_from_syserror (); goto leave; } ksba_cert_ref (subject_cert); ci->cert = subject_cert; ci->next = chain; chain = ci; } xfree (issuer); xfree (subject); issuer = ksba_cert_get_issuer (subject_cert, 0); subject = ksba_cert_get_subject (subject_cert, 0); if (!issuer) { do_list (1, listmode, listfp, _("no issuer found in certificate")); rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } /* Is this a self-issued certificate (i.e. the root certificate)? */ is_root = is_root_cert (subject_cert, issuer, subject); if (is_root) { chain->is_root = 1; /* Check early whether the certificate is listed as trusted. We used to do this only later but changed it to call the check right here so that we can access special flags associated with that specific root certificate. */ if (gpgsm_cert_has_well_known_private_key (subject_cert)) { memset (rootca_flags, 0, sizeof *rootca_flags); istrusted_rc = ((flags & VALIDATE_FLAG_STEED) ? 0 : gpg_error (GPG_ERR_NOT_TRUSTED)); } else istrusted_rc = gpgsm_agent_istrusted (ctrl, subject_cert, NULL, rootca_flags); audit_log_cert (ctrl->audit, AUDIT_ROOT_TRUSTED, subject_cert, istrusted_rc); /* If the chain model extended attribute is used, make sure that our chain model flag is set. */ if (!(flags & VALIDATE_FLAG_STEED) && has_validation_model_chain (subject_cert, listmode, listfp)) rootca_flags->chain_model = 1; } /* Check the validity period. */ if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) ) rc = check_validity_period_cm (current_time, check_time, subject_cert, exptime, listmode, listfp, (depth && is_root)? -1: depth); else rc = check_validity_period (current_time, subject_cert, exptime, listmode, listfp, (depth && is_root)? -1: depth); if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED) any_expired = 1; else if (rc) goto leave; /* Assert that we understand all critical extensions. */ rc = unknown_criticals (subject_cert, listmode, listfp); if (rc) goto leave; /* Do a policy check. */ if (!opt.no_policy_check) { rc = check_cert_policy (subject_cert, listmode, listfp); if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH) { any_no_policy_match = 1; rc = 1; /* Be on the safe side and set RC. */ } else if (rc) goto leave; } /* If this is the root certificate we are at the end of the chain. */ if (is_root) { if (!istrusted_rc) ; /* No need to check the certificate for a trusted one. */ else if (gpgsm_check_cert_sig (subject_cert, subject_cert) ) { /* We only check the signature if the certificate is not trusted for better diagnostics. */ do_list (1, listmode, listfp, _("self-signed certificate has a BAD signature")); if (DBG_X509) { gpgsm_dump_cert ("self-signing cert", subject_cert); } rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN : GPG_ERR_BAD_CERT); goto leave; } if (!rootca_flags->relax) { rc = allowed_ca (ctrl, subject_cert, NULL, listmode, listfp); if (rc) goto leave; } /* Set the flag for qualified signatures. This flag is deduced from a list of root certificates allowed for qualified signatures. */ if (is_qualified == -1 && !(flags & VALIDATE_FLAG_STEED)) { gpg_error_t err; size_t buflen; char buf[1]; if (!ksba_cert_get_user_data (cert, "is_qualified", &buf, sizeof (buf), &buflen) && buflen) { /* We already checked this for this certificate, thus we simply take it from the user data. */ is_qualified = !!*buf; } else { /* Need to consult the list of root certificates for qualified signatures. But first we check the modern way by looking at the root ca flag. */ if (rootca_flags->qualified) err = 0; else err = gpgsm_is_in_qualified_list (ctrl, subject_cert, NULL); if (!err) is_qualified = 1; else if ( gpg_err_code (err) == GPG_ERR_NOT_FOUND) is_qualified = 0; else log_error ("checking the list of qualified " "root certificates failed: %s\n", gpg_strerror (err)); if ( is_qualified != -1 ) { /* Cache the result but don't care too much about an error. */ buf[0] = !!is_qualified; err = ksba_cert_set_user_data (subject_cert, "is_qualified", buf, 1); if (err) log_error ("set_user_data(is_qualified) failed: %s\n", gpg_strerror (err)); } } } /* Act on the check for a trusted root certificates. */ rc = istrusted_rc; if (!rc) ; else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) { do_list (0, listmode, listfp, _("root certificate is not marked trusted")); /* If we already figured out that the certificate is expired it does not make much sense to ask the user whether they want to trust the root certificate. We should do this only if the certificate under question will then be usable. If the certificate has a well known private key asking the user does not make any sense. */ if ( !any_expired && !gpgsm_cert_has_well_known_private_key (subject_cert) && (!listmode || !already_asked_marktrusted (subject_cert)) && ask_marktrusted (ctrl, subject_cert, listmode) ) rc = 0; } else { log_error (_("checking the trust list failed: %s\n"), gpg_strerror (rc)); } if (rc) goto leave; /* Check for revocations etc. */ if ((flags & VALIDATE_FLAG_NO_DIRMNGR)) ; else if ((flags & VALIDATE_FLAG_STEED)) ; /* Fixme: check revocations via DNS. */ else if (opt.no_trusted_cert_crl_check || rootca_flags->relax) ; else rc = is_cert_still_valid (ctrl, (flags & VALIDATE_FLAG_CHAIN_MODEL), listmode, listfp, subject_cert, subject_cert, &any_revoked, &any_no_crl, &any_crl_too_old); if (rc) goto leave; break; /* Okay: a self-signed certificate is an end-point. */ } /* End is_root. */ /* Take care that the chain does not get too long. */ if ((depth+1) > maxdepth) { do_list (1, listmode, listfp, _("certificate chain too long\n")); rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } /* Find the next cert up the tree. */ keydb_search_reset (kh); rc = find_up (ctrl, kh, subject_cert, issuer, 0); if (rc) { if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { do_list (0, listmode, listfp, _("issuer certificate not found")); if (!listmode && !opt.quiet) { log_info ("issuer certificate: #/"); gpgsm_dump_string (issuer); log_printf ("\n"); } } else log_error ("failed to find issuer's certificate: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc)); rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); goto leave; } ksba_cert_release (issuer_cert); issuer_cert = NULL; rc = keydb_get_cert (kh, &issuer_cert); if (rc) { log_error ("keydb_get_cert failed in %s: %s <%s>\n", __func__, gpg_strerror (rc), gpg_strsource (rc)); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } try_another_cert: if (DBG_X509) { log_debug ("got issuer's certificate:\n"); gpgsm_dump_cert ("issuer", issuer_cert); } rc = gpgsm_check_cert_sig (issuer_cert, subject_cert); if (rc) { do_list (0, listmode, listfp, _("certificate has a BAD signature")); if (DBG_X509) { gpgsm_dump_cert ("signing issuer", issuer_cert); gpgsm_dump_cert ("signed subject", subject_cert); } if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE) { /* We now try to find other issuer certificates which might have been used. This is required because some CAs are reusing the issuer and subject DN for new root certificates. */ /* FIXME: Do this only if we don't have an AKI.keyIdentifier */ rc = find_up (ctrl, kh, subject_cert, issuer, 1); if (!rc) { ksba_cert_t tmp_cert; rc = keydb_get_cert (kh, &tmp_cert); if (rc || !compare_certs (issuer_cert, tmp_cert)) { /* The find next did not work or returned an identical certificate. We better stop here to avoid infinite checks. */ /* No need to set RC because it is not used: rc = gpg_error (GPG_ERR_BAD_SIGNATURE); */ ksba_cert_release (tmp_cert); } else { do_list (0, listmode, listfp, _("found another possible matching " "CA certificate - trying again")); ksba_cert_release (issuer_cert); issuer_cert = tmp_cert; goto try_another_cert; } } } /* We give a more descriptive error code than the one returned from the signature checking. */ rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } is_root = gpgsm_is_root_cert (issuer_cert); istrusted_rc = gpg_error (GPG_ERR_NOT_TRUSTED); /* Check that a CA is allowed to issue certificates. */ { int chainlen; rc = allowed_ca (ctrl, issuer_cert, &chainlen, listmode, listfp); if (rc) { /* Not allowed. Check whether this is a trusted root certificate and whether we allow special exceptions. We could carry the result of the test over to the regular root check at the top of the loop but for clarity we won't do that. Given that the majority of certificates carry proper BasicContraints our way of overriding an error in the way is justified for performance reasons. */ if (is_root) { if (gpgsm_cert_has_well_known_private_key (issuer_cert)) { memset (rootca_flags, 0, sizeof *rootca_flags); istrusted_rc = ((flags & VALIDATE_FLAG_STEED) ? 0 : gpg_error (GPG_ERR_NOT_TRUSTED)); } else istrusted_rc = gpgsm_agent_istrusted (ctrl, issuer_cert, NULL, rootca_flags); if (!istrusted_rc && rootca_flags->relax) { /* Ignore the error due to the relax flag. */ rc = 0; chainlen = -1; } } } if (rc) goto leave; if (chainlen >= 0 && depth > chainlen) { do_list (1, listmode, listfp, _("certificate chain longer than allowed by CA (%d)"), chainlen); rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } } /* Is the certificate allowed to sign other certificates. */ if (!listmode) { rc = gpgsm_cert_use_cert_p (issuer_cert); if (rc) { char numbuf[50]; sprintf (numbuf, "%d", rc); gpgsm_status2 (ctrl, STATUS_ERROR, "certcert.issuer.keyusage", numbuf, NULL); goto leave; } } /* Check for revocations etc. Note that for a root certificate this test is done a second time later. This should eventually be fixed. */ if ((flags & VALIDATE_FLAG_NO_DIRMNGR)) rc = 0; else if ((flags & VALIDATE_FLAG_STEED)) rc = 0; /* Fixme: XXX */ else if (is_root && (opt.no_trusted_cert_crl_check || (!istrusted_rc && rootca_flags->relax))) rc = 0; else rc = is_cert_still_valid (ctrl, (flags & VALIDATE_FLAG_CHAIN_MODEL), listmode, listfp, subject_cert, issuer_cert, &any_revoked, &any_no_crl, &any_crl_too_old); if (rc) goto leave; if (opt.verbose && !listmode) log_info (depth == 0 ? _("certificate is good\n") : !is_root ? _("intermediate certificate is good\n") : /* other */ _("root certificate is good\n")); /* Under the chain model the next check time is the creation time of the subject certificate. */ if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) ) { rc = ksba_cert_get_validity (subject_cert, 0, check_time); if (rc) { /* That will never happen as we have already checked this above. */ BUG (); } } /* For the next round the current issuer becomes the new subject. */ keydb_search_reset (kh); ksba_cert_release (subject_cert); subject_cert = issuer_cert; issuer_cert = NULL; depth++; } /* End chain traversal. */ if (!listmode && !opt.quiet) { if (opt.no_policy_check) log_info ("policies not checked due to %s option\n", "--disable-policy-checks"); if (ctrl->offline || (opt.no_crl_check && !ctrl->use_ocsp)) log_info ("CRLs not checked due to %s option\n", ctrl->offline ? "offline" : "--disable-crl-checks"); } if (!rc) { /* If we encountered an error somewhere during the checks, set the error code to the most critical one */ if (any_revoked) rc = gpg_error (GPG_ERR_CERT_REVOKED); else if (any_expired) rc = gpg_error (GPG_ERR_CERT_EXPIRED); else if (any_no_crl) rc = gpg_error (GPG_ERR_NO_CRL_KNOWN); else if (any_crl_too_old) rc = gpg_error (GPG_ERR_CRL_TOO_OLD); else if (any_no_policy_match) rc = gpg_error (GPG_ERR_NO_POLICY_MATCH); } leave: /* If we have traversed a complete chain up to the root we will reset the ephemeral flag for all these certificates. This is done regardless of any error because those errors may only be transient. */ if (chain && chain->is_root) { gpg_error_t err; chain_item_t ci; for (ci = chain; ci; ci = ci->next) { /* Note that it is possible for the last certificate in the chain (i.e. our target certificate) that it has not yet been stored in the keybox and thus the flag can't be set. We ignore this error because it will later be stored anyway. */ err = keydb_set_cert_flags (ctrl, ci->cert, 1, KEYBOX_FLAG_BLOB, 0, KEYBOX_FLAG_BLOB_EPHEMERAL, 0); if (!ci->next && gpg_err_code (err) == GPG_ERR_NOT_FOUND) ; else if (err) log_error ("clearing ephemeral flag failed: %s\n", gpg_strerror (err)); } } /* If we have figured something about the qualified signature capability of the certificate under question, store the result as user data in all certificates of the chain. We do this even if the validation itself failed. */ if (is_qualified != -1 && !(flags & VALIDATE_FLAG_STEED)) { gpg_error_t err; chain_item_t ci; char buf[1]; buf[0] = !!is_qualified; for (ci = chain; ci; ci = ci->next) { err = ksba_cert_set_user_data (ci->cert, "is_qualified", buf, 1); if (err) { log_error ("set_user_data(is_qualified) failed: %s\n", gpg_strerror (err)); if (!rc) rc = err; } } } /* If auditing has been enabled, record what is in the chain. */ if (ctrl->audit) { chain_item_t ci; audit_log (ctrl->audit, AUDIT_CHAIN_BEGIN); for (ci = chain; ci; ci = ci->next) { audit_log_cert (ctrl->audit, ci->is_root? AUDIT_CHAIN_ROOTCERT : AUDIT_CHAIN_CERT, ci->cert, 0); } audit_log (ctrl->audit, AUDIT_CHAIN_END); } if (r_exptime) gnupg_copy_time (r_exptime, exptime); xfree (issuer); xfree (subject); keydb_release (kh); while (chain) { chain_item_t ci_next = chain->next; ksba_cert_release (chain->cert); xfree (chain); chain = ci_next; } ksba_cert_release (issuer_cert); ksba_cert_release (subject_cert); return rc; } /* Validate a certificate chain. For a description see do_validate_chain. This function is a wrapper to handle a root certificate with the chain_model flag set. If RETFLAGS is not NULL, flags indicating now the verification was done are stored there. The only defined bits for RETFLAGS are VALIDATE_FLAG_CHAIN_MODEL and VALIDATE_FLAG_STEED. If you are verifying a signature you should set CHECKTIME to the creation time of the signature. If your are verifying a certificate, set it nil (i.e. the empty string). If the creation date of the signature is not known use the special date "19700101T000000" which is treated in a special way here. */ int gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime, ksba_isotime_t r_exptime, int listmode, estream_t listfp, unsigned int flags, unsigned int *retflags) { int rc; struct rootca_flags_s rootca_flags; unsigned int dummy_retflags; if (!retflags) retflags = &dummy_retflags; /* If the session requested a certain validation mode make sure the corresponding flags are set. */ if (ctrl->validation_model == 1) flags |= VALIDATE_FLAG_CHAIN_MODEL; else if (ctrl->validation_model == 2) flags |= VALIDATE_FLAG_STEED; /* If the chain model was forced, set this immediately into RETFLAGS. */ *retflags = (flags & VALIDATE_FLAG_CHAIN_MODEL); memset (&rootca_flags, 0, sizeof rootca_flags); rc = do_validate_chain (ctrl, cert, checktime, r_exptime, listmode, listfp, flags, &rootca_flags); if (!rc && (flags & VALIDATE_FLAG_STEED)) { *retflags |= VALIDATE_FLAG_STEED; } else if (!(flags & VALIDATE_FLAG_CHAIN_MODEL) && (rootca_flags.valid && rootca_flags.chain_model)) { /* The root CA indicated that the chain model is to be used but * we have not yet used it. Thus do the validation again using * the chain model. */ do_list (0, listmode, listfp, _("switching to chain model")); rc = do_validate_chain (ctrl, cert, checktime, r_exptime, listmode, listfp, (flags |= VALIDATE_FLAG_CHAIN_MODEL), &rootca_flags); *retflags |= VALIDATE_FLAG_CHAIN_MODEL; } if (opt.verbose) do_list (0, listmode, listfp, _("validation model used: %s"), (*retflags & VALIDATE_FLAG_STEED)? "steed" : (*retflags & VALIDATE_FLAG_CHAIN_MODEL)? _("chain"):_("shell")); return rc; } /* Check that the given certificate is valid but DO NOT check any constraints. We assume that the issuers certificate is already in the DB and that this one is valid; which it should be because it has been checked using this function. */ int gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert) { int rc = 0; char *issuer = NULL; char *subject = NULL; KEYDB_HANDLE kh; ksba_cert_t issuer_cert = NULL; if (opt.no_chain_validation) { log_info ("WARNING: bypassing basic certificate checks\n"); return 0; } kh = keydb_new (ctrl); if (!kh) { log_error (_("failed to allocate keyDB handle\n")); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } issuer = ksba_cert_get_issuer (cert, 0); subject = ksba_cert_get_subject (cert, 0); if (!issuer) { log_error ("no issuer found in certificate\n"); rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } if (is_root_cert (cert, issuer, subject)) { rc = gpgsm_check_cert_sig (cert, cert); if (rc) { log_error ("self-signed certificate has a BAD signature: %s\n", gpg_strerror (rc)); if (DBG_X509) { gpgsm_dump_cert ("self-signing cert", cert); } rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } } else { /* Find the next cert up the tree. */ keydb_search_reset (kh); rc = find_up (ctrl, kh, cert, issuer, 0); if (rc) { if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { if (!opt.quiet) { log_info ("issuer certificate (#/"); gpgsm_dump_string (issuer); log_printf (") not found\n"); } } else log_error ("failed to find issuer's certificate: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc)); rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); goto leave; } ksba_cert_release (issuer_cert); issuer_cert = NULL; rc = keydb_get_cert (kh, &issuer_cert); if (rc) { log_error ("keydb_get_cert failed in %s: %s <%s>\n", __func__, gpg_strerror (rc), gpg_strsource (rc)); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } rc = gpgsm_check_cert_sig (issuer_cert, cert); if (rc) { log_error ("certificate has a BAD signature: %s\n", gpg_strerror (rc)); if (DBG_X509) { gpgsm_dump_cert ("signing issuer", issuer_cert); gpgsm_dump_cert ("signed subject", cert); } rc = gpg_error (GPG_ERR_BAD_CERT); goto leave; } if (opt.verbose) log_info (_("certificate is good\n")); } leave: xfree (issuer); xfree (subject); keydb_release (kh); ksba_cert_release (issuer_cert); return rc; } /* Check whether the certificate CERT has been issued by the German authority for qualified signature. They do not set the basicConstraints and thus we need this workaround. It works by looking up the root certificate and checking whether that one is listed as a qualified certificate for Germany. We also try to cache this data but as long as don't keep a reference to the certificate this won't be used. Returns: True if CERT is a RegTP issued CA cert (i.e. the root certificate itself or one of the CAs). In that case CHAINLEN will receive the length of the chain which is either 0 or 1. */ static int get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen) { gpg_error_t err; ksba_cert_t next; int rc = 0; int i, depth; char country[3]; ksba_cert_t array[4]; char buf[2]; size_t buflen; int dummy_chainlen; if (!chainlen) chainlen = &dummy_chainlen; *chainlen = 0; err = ksba_cert_get_user_data (cert, "regtp_ca_chainlen", &buf, sizeof (buf), &buflen); if (!err) { /* Got info. */ if (buflen < 2 || !*buf) return 0; /* Nothing found. */ *chainlen = buf[1]; return 1; /* This is a regtp CA. */ } else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) { log_error ("ksba_cert_get_user_data(%s) failed: %s\n", "regtp_ca_chainlen", gpg_strerror (err)); return 0; /* Nothing found. */ } /* Need to gather the info. This requires to walk up the chain until we have found the root. Because we are only interested in German Bundesnetzagentur (former RegTP) derived certificates 3 levels are enough. (The German signature law demands a 3 tier hierarchy; thus there is only one CA between the EE and the Root CA.) */ memset (&array, 0, sizeof array); depth = 0; ksba_cert_ref (cert); array[depth++] = cert; ksba_cert_ref (cert); while (depth < DIM(array) && !(rc=gpgsm_walk_cert_chain (ctrl, cert, &next))) { ksba_cert_release (cert); ksba_cert_ref (next); array[depth++] = next; cert = next; } ksba_cert_release (cert); if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND || !depth || depth == DIM(array) ) { /* We did not reached the root. */ goto leave; } /* If this is a German signature law issued certificate, we store additional information. */ if (!gpgsm_is_in_qualified_list (NULL, array[depth-1], country) && !strcmp (country, "de")) { /* Setting the pathlen for the root CA and the CA flag for the next one is all what we need to do. */ err = ksba_cert_set_user_data (array[depth-1], "regtp_ca_chainlen", "\x01\x01", 2); if (!err && depth > 1) err = ksba_cert_set_user_data (array[depth-2], "regtp_ca_chainlen", "\x01\x00", 2); if (err) log_error ("ksba_set_user_data(%s) failed: %s\n", "regtp_ca_chainlen", gpg_strerror (err)); for (i=0; i < depth; i++) ksba_cert_release (array[i]); *chainlen = (depth>1? 0:1); return 1; } leave: /* Nothing special with this certificate. Mark the target certificate anyway to avoid duplicate lookups. */ err = ksba_cert_set_user_data (cert, "regtp_ca_chainlen", "", 1); if (err) log_error ("ksba_set_user_data(%s) failed: %s\n", "regtp_ca_chainlen", gpg_strerror (err)); for (i=0; i < depth; i++) ksba_cert_release (array[i]); return 0; } diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 3247a0f2e..f8b3856c2 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -1,2379 +1,2383 @@ /* gpgsm.c - GnuPG for S/MIME * Copyright (C) 2001-2020 Free Software Foundation, Inc. * Copyright (C) 2001-2019 Werner Koch * Copyright (C) 2015-2021 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #include /*#include */ #include #define INCLUDED_BY_MAIN_MODULE 1 #include "gpgsm.h" #include #include /* malloc hooks */ #include "passphrase.h" #include "../common/shareddefs.h" #include "../kbx/keybox.h" /* malloc hooks */ #include "../common/i18n.h" #include "keydb.h" #include "../common/sysutils.h" #include "../common/gc-opt-flags.h" #include "../common/asshelp.h" #include "../common/init.h" #include "../common/compliance.h" #include "../common/comopt.h" #include "minip12.h" #ifndef O_BINARY #define O_BINARY 0 #endif enum cmd_and_opt_values { aNull = 0, oArmor = 'a', aDetachedSign = 'b', aSym = 'c', aDecrypt = 'd', aEncr = 'e', aListKeys = 'k', aListSecretKeys = 'K', oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oRecipient = 'r', aSign = 's', oUser = 'u', oVerbose = 'v', oBatch = 500, aClearsign, aKeygen, aSignEncr, aDeleteKey, aImport, aVerify, aListExternalKeys, aListChain, aSendKeys, aRecvKeys, aExport, aExportSecretKeyP12, aExportSecretKeyP8, aExportSecretKeyRaw, aServer, aLearnCard, aCallDirmngr, aCallProtectTool, aPasswd, aGPGConfList, aGPGConfTest, aDumpKeys, aDumpChain, aDumpSecretKeys, aDumpExternalKeys, aShowCerts, aKeydbClearSomeCertFlags, aFingerprint, oOptions, oDebug, oDebugLevel, oDebugAll, oDebugNone, oDebugWait, oDebugAllowCoreDump, oDebugNoChainValidation, oDebugIgnoreExpiration, oDebugForceECDHSHA1KDF, oLogFile, oNoLogFile, oAuditLog, oHtmlAuditLog, oEnableSpecialFilenames, oAgentProgram, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oPreferSystemDirmngr, oDirmngrProgram, oDisableDirmngr, oProtectToolProgram, oFakedSystemTime, oPassphraseFD, oPinentryMode, oRequestOrigin, oAssumeArmor, oAssumeBase64, oAssumeBinary, oBase64, oNoArmor, oP12Charset, oCompliance, oDisableCRLChecks, oEnableCRLChecks, oDisableTrustedCertCRLCheck, oEnableTrustedCertCRLCheck, oForceCRLRefresh, oEnableIssuerBasedCRLCheck, oDisableOCSP, oEnableOCSP, oIncludeCerts, oPolicyFile, oDisablePolicyChecks, oEnablePolicyChecks, oAutoIssuerKeyRetrieve, oMinRSALength, oWithFingerprint, oWithMD5Fingerprint, oWithKeygrip, oWithSecret, oWithKeyScreening, oAnswerYes, oAnswerNo, oKeyring, oDefaultKey, oDefRecipient, oDefRecipientSelf, oNoDefRecipient, oStatusFD, oCipherAlgo, oDigestAlgo, oExtraDigestAlgo, oNoVerbose, oNoSecmemWarn, oNoDefKeyring, oNoGreeting, oNoTTY, oNoOptions, oNoBatch, oHomedir, oWithColons, oWithKeyData, oWithValidation, oWithEphemeralKeys, oSkipVerify, oValidationModel, oKeyServer, oKeyServer_deprecated, oEncryptTo, oNoEncryptTo, oLoggerFD, oDisableCipherAlgo, oDisablePubkeyAlgo, oIgnoreTimeConflict, oNoRandomSeedFile, oNoCommonCertsImport, oIgnoreCertExtension, oIgnoreCertWithOID, oAuthenticode, oAttribute, oChUid, oUseKeyboxd, oKeyboxdProgram, oRequireCompliance, oCompatibilityFlags, oNoAutostart }; static gpgrt_opt_t opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aSign, "sign", N_("make a signature")), /*ARGPARSE_c (aClearsign, "clearsign", N_("make a clear text signature") ),*/ ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")), ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")), /*ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")),*/ ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")), ARGPARSE_c (aVerify, "verify", N_("verify a signature")), ARGPARSE_c (aListKeys, "list-keys", N_("list keys")), ARGPARSE_c (aListExternalKeys, "list-external-keys", N_("list external keys")), ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")), ARGPARSE_c (aListChain, "list-chain", N_("list certificate chain")), ARGPARSE_c (aFingerprint, "fingerprint", N_("list keys and fingerprints")), ARGPARSE_c (aKeygen, "generate-key", N_("generate a new key pair")), ARGPARSE_c (aKeygen, "gen-key", "@"), ARGPARSE_c (aDeleteKey, "delete-keys", N_("remove keys from the public keyring")), /*ARGPARSE_c (aSendKeys, "send-keys", N_("export keys to a keyserver")),*/ /*ARGPARSE_c (aRecvKeys, "recv-keys", N_("import keys from a keyserver")),*/ ARGPARSE_c (aImport, "import", N_("import certificates")), ARGPARSE_c (aExport, "export", N_("export certificates")), /* We use -raw and not -p1 for pkcs#1 secret key export so that it won't accidentally be used in case -p12 was intended. */ ARGPARSE_c (aExportSecretKeyP12, "export-secret-key-p12", "@"), ARGPARSE_c (aExportSecretKeyP8, "export-secret-key-p8", "@"), ARGPARSE_c (aExportSecretKeyRaw, "export-secret-key-raw", "@"), ARGPARSE_c (aLearnCard, "learn-card", N_("register a smartcard")), ARGPARSE_c (aServer, "server", N_("run in server mode")), ARGPARSE_c (aCallDirmngr, "call-dirmngr", N_("pass a command to the dirmngr")), ARGPARSE_c (aCallProtectTool, "call-protect-tool", N_("invoke gpg-protect-tool")), ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")), ARGPARSE_c (aPasswd, "passwd", "@"), ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_c (aShowCerts, "show-certs", "@"), ARGPARSE_c (aDumpKeys, "dump-cert", "@"), ARGPARSE_c (aDumpKeys, "dump-keys", "@"), ARGPARSE_c (aDumpChain, "dump-chain", "@"), ARGPARSE_c (aDumpExternalKeys, "dump-external-keys", "@"), ARGPARSE_c (aDumpSecretKeys, "dump-secret-keys", "@"), ARGPARSE_c (aKeydbClearSomeCertFlags, "keydb-clear-some-cert-flags", "@"), ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oNoTTY, "no-tty", N_("don't use the terminal at all")), ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", N_("|LEVEL|set the debugging level to LEVEL")), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_n (oDebugNone, "debug-none", "@"), ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"), ARGPARSE_s_n (oDebugNoChainValidation, "debug-no-chain-validation", "@"), ARGPARSE_s_n (oDebugIgnoreExpiration, "debug-ignore-expiration", "@"), ARGPARSE_s_n (oDebugForceECDHSHA1KDF, "debug-force-ecdh-sha1kdf", "@"), ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write server mode logs to FILE")), ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"), ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"), ARGPARSE_header ("Configuration", N_("Options controlling the configuration")), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_n (oPreferSystemDirmngr,"prefer-system-dirmngr", "@"), ARGPARSE_s_s (oValidationModel, "validation-model", "@"), ARGPARSE_s_i (oIncludeCerts, "include-certs", N_("|N|number of certificates to include") ), ARGPARSE_s_s (oPolicyFile, "policy-file", N_("|FILE|take policy information from FILE")), ARGPARSE_s_s (oCompliance, "compliance", "@"), ARGPARSE_p_u (oMinRSALength, "min-rsa-length", "@"), ARGPARSE_s_n (oNoCommonCertsImport, "no-common-certs-import", "@"), ARGPARSE_s_s (oIgnoreCertExtension, "ignore-cert-extension", "@"), ARGPARSE_s_s (oIgnoreCertWithOID, "ignore-cert-with-oid", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oKeyboxdProgram, "keyboxd-program", "@"), ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"), ARGPARSE_s_s (oProtectToolProgram, "protect-tool-program", "@"), ARGPARSE_header ("Input", N_("Options controlling the input")), ARGPARSE_s_n (oAssumeArmor, "assume-armor", N_("assume input is in PEM format")), ARGPARSE_s_n (oAssumeBase64, "assume-base64", N_("assume input is in base-64 format")), ARGPARSE_s_n (oAssumeBinary, "assume-binary", N_("assume input is in binary format")), ARGPARSE_header ("Output", N_("Options controlling the output")), ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")), ARGPARSE_s_n (oArmor, "armour", "@"), ARGPARSE_s_n (oNoArmor, "no-armor", "@"), ARGPARSE_s_n (oNoArmor, "no-armour", "@"), ARGPARSE_s_n (oBase64, "base64", N_("create base-64 encoded output")), ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")), ARGPARSE_s_n (oAuthenticode, "authenticode", "@"), ARGPARSE_s_s (oAttribute, "attribute", "@"), ARGPARSE_header (NULL, N_("Options to specify keys")), ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")), ARGPARSE_s_s (oUser, "local-user", N_("|USER-ID|use USER-ID to sign or decrypt")), ARGPARSE_s_s (oDefaultKey, "default-key", N_("|USER-ID|use USER-ID as default secret key")), ARGPARSE_s_s (oEncryptTo, "encrypt-to", N_("|NAME|encrypt to user ID NAME as well")), ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"), /* Not yet used: */ /* ARGPARSE_s_s (oDefRecipient, "default-recipient", */ /* N_("|NAME|use NAME as default recipient")), */ /* ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", */ /* N_("use the default key as default recipient")), */ /* ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"), */ ARGPARSE_s_s (oKeyring, "keyring", N_("|FILE|add keyring to the list of keyrings")), ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"), ARGPARSE_s_s (oKeyServer_deprecated, "ldapserver", "@"), ARGPARSE_s_s (oKeyServer, "keyserver", "@"), ARGPARSE_s_n (oUseKeyboxd, "use-keyboxd", "@"), ARGPARSE_header ("ImportExport", N_("Options controlling key import and export")), ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr", N_("disable all access to the dirmngr")), ARGPARSE_s_n (oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve", N_("fetch missing issuer certificates")), ARGPARSE_s_s (oP12Charset, "p12-charset", N_("|NAME|use encoding NAME for PKCS#12 passphrases")), ARGPARSE_header ("Keylist", N_("Options controlling key listings")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"), ARGPARSE_s_n (oWithValidation, "with-validation", "@"), ARGPARSE_s_n (oWithMD5Fingerprint, "with-md5-fingerprint", "@"), ARGPARSE_s_n (oWithEphemeralKeys, "with-ephemeral-keys", "@"), ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"), ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"), ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"), ARGPARSE_s_n (oWithSecret, "with-secret", "@"), ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"), ARGPARSE_header ("Security", N_("Options controlling the security")), ARGPARSE_s_n (oDisableCRLChecks, "disable-crl-checks", N_("never consult a CRL")), ARGPARSE_s_n (oEnableCRLChecks, "enable-crl-checks", "@"), ARGPARSE_s_n (oDisableTrustedCertCRLCheck, "disable-trusted-cert-crl-check", N_("do not check CRLs for root certificates")), ARGPARSE_s_n (oEnableTrustedCertCRLCheck, "enable-trusted-cert-crl-check", "@"), ARGPARSE_s_n (oDisableOCSP, "disable-ocsp", "@"), ARGPARSE_s_n (oEnableOCSP, "enable-ocsp", N_("check validity using OCSP")), ARGPARSE_s_n (oDisablePolicyChecks, "disable-policy-checks", N_("do not check certificate policies")), ARGPARSE_s_n (oEnablePolicyChecks, "enable-policy-checks", "@"), ARGPARSE_s_s (oCipherAlgo, "cipher-algo", N_("|NAME|use cipher algorithm NAME")), ARGPARSE_s_s (oDigestAlgo, "digest-algo", N_("|NAME|use message digest algorithm NAME")), ARGPARSE_s_s (oExtraDigestAlgo, "extra-digest-algo", "@"), ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"), ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"), ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"), ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"), ARGPARSE_s_n (oRequireCompliance, "require-compliance", "@"), ARGPARSE_header (NULL, N_("Options for unattended use")), ARGPARSE_s_n (oBatch, "batch", N_("batch mode: never ask")), ARGPARSE_s_n (oNoBatch, "no-batch", "@"), ARGPARSE_s_n (oAnswerYes, "yes", N_("assume yes on most questions")), ARGPARSE_s_n (oAnswerNo, "no", N_("assume no on most questions")), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"), ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"), ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"), ARGPARSE_header (NULL, N_("Other options")), ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_noconffile (oNoOptions, "no-options", "@"), ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), ARGPARSE_s_s (oRequestOrigin, "request-origin", "@"), ARGPARSE_s_n (oForceCRLRefresh, "force-crl-refresh", "@"), ARGPARSE_s_n (oEnableIssuerBasedCRLCheck, "enable-issuer-based-crl-check", "@"), ARGPARSE_s_s (oAuditLog, "audit-log", N_("|FILE|write an audit log to FILE")), ARGPARSE_s_s (oHtmlAuditLog, "html-audit-log", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages", "@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_s_s (oCompatibilityFlags, "compatibility-flags", "@"), ARGPARSE_header (NULL, ""), /* Stop the header group. */ /* Command aliases. */ ARGPARSE_c (aListKeys, "list-key", "@"), ARGPARSE_c (aListChain, "list-signatures", "@"), ARGPARSE_c (aListChain, "list-sigs", "@"), ARGPARSE_c (aListChain, "check-signatures", "@"), ARGPARSE_c (aListChain, "check-sigs", "@"), ARGPARSE_c (aDeleteKey, "delete-key", "@"), ARGPARSE_group (302, N_( "@\n(See the man page for a complete listing of all commands and options)\n" )), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_X509_VALUE , "x509" }, { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { DBG_CLOCK_VALUE , "clock" }, { DBG_LOOKUP_VALUE , "lookup" }, { 0, NULL } }; /* The list of compatibility flags. */ static struct compatibility_flags_s compatibility_flags [] = { { COMPAT_ALLOW_KA_TO_ENCR, "allow-ka-to-encr" }, { 0, NULL } }; /* Global variable to keep an error count. */ int gpgsm_errors_seen = 0; /* It is possible that we are currentlu running under setuid permissions */ static int maybe_setuid = 1; /* Helper to implement --debug-level and --debug*/ static const char *debug_level; static unsigned int debug_value; /* Default value for include-certs. We need an extra macro for gpgconf-list because the variable will be changed by the command line option. It is often cumbersome to locate intermediate certificates, thus by default we include all certificates in the chain. However we leave out the root certificate because that would make it too easy for the recipient to import that root certificate. A root certificate should be installed only after due checks and thus it won't help to send it along with each message. */ #define DEFAULT_INCLUDE_CERTS -2 /* Include all certs but root. */ static int default_include_certs = DEFAULT_INCLUDE_CERTS; /* Whether the chain mode shall be used for validation. */ static int default_validation_model; /* The default cipher algo. */ #define DEFAULT_CIPHER_ALGO "AES256" static char *build_list (const char *text, const char *(*mapf)(int), int (*chkf)(int)); static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd ); static void emergency_cleanup (void); static int open_read (const char *filename); static estream_t open_es_fread (const char *filename, const char *mode); static estream_t open_es_fwrite (const char *filename); static void run_protect_tool (int argc, char **argv); static int our_pk_test_algo (int algo) { switch (algo) { case GCRY_PK_RSA: case GCRY_PK_ECDSA: case GCRY_PK_EDDSA: return gcry_pk_test_algo (algo); default: return 1; } } static int our_cipher_test_algo (int algo) { switch (algo) { case GCRY_CIPHER_3DES: case GCRY_CIPHER_AES128: case GCRY_CIPHER_AES192: case GCRY_CIPHER_AES256: case GCRY_CIPHER_SERPENT128: case GCRY_CIPHER_SERPENT192: case GCRY_CIPHER_SERPENT256: case GCRY_CIPHER_SEED: case GCRY_CIPHER_CAMELLIA128: case GCRY_CIPHER_CAMELLIA192: case GCRY_CIPHER_CAMELLIA256: return gcry_cipher_test_algo (algo); default: return 1; } } static int our_md_test_algo (int algo) { switch (algo) { case GCRY_MD_MD5: case GCRY_MD_SHA1: case GCRY_MD_RMD160: case GCRY_MD_SHA224: case GCRY_MD_SHA256: case GCRY_MD_SHA384: case GCRY_MD_SHA512: case GCRY_MD_WHIRLPOOL: return gcry_md_test_algo (algo); default: return 1; } } /* nPth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) { const char *s; char *result; if (maybe_setuid) { gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ maybe_setuid = 0; } s = getfnc (NULL); result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); strcpy (stpcpy (stpcpy (result, libname), " "), s); return result; } static const char * my_strusage( int level ) { static char *digests, *pubkeys, *ciphers; static char *ver_gcry, *ver_ksba; const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPGSM@ (@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: @GPGSM@ [options] [files] (-h for help)"); break; case 41: p = _("Syntax: @GPGSM@ [options] [files]\n" "Sign, check, encrypt or decrypt using the S/MIME protocol\n" "Default operation depends on the input data\n"); break; case 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; case 21: if (!ver_ksba) ver_ksba = make_libversion ("libksba", ksba_check_version); p = ver_ksba; break; case 31: p = "\nHome: "; break; case 32: p = gnupg_homedir (); break; case 33: p = _("\nSupported algorithms:\n"); break; case 34: if (!ciphers) ciphers = build_list ("Cipher: ", gnupg_cipher_algo_name, our_cipher_test_algo ); p = ciphers; break; case 35: if (!pubkeys) pubkeys = build_list ("Pubkey: ", gcry_pk_algo_name, our_pk_test_algo ); p = pubkeys; break; case 36: if (!digests) digests = build_list("Hash: ", gcry_md_algo_name, our_md_test_algo ); p = digests; break; default: p = NULL; break; } return p; } static char * build_list (const char *text, const char * (*mapf)(int), int (*chkf)(int)) { int i; size_t n=strlen(text)+2; char *list, *p; if (maybe_setuid) { gcry_control (GCRYCTL_DROP_PRIVS); /* drop setuid */ } for (i=1; i < 400; i++ ) if (!chkf(i)) n += strlen(mapf(i)) + 2; list = xmalloc (21 + n); *list = 0; for (p=NULL, i=1; i < 400; i++) { if (!chkf(i)) { if( !p ) p = stpcpy (list, text ); else p = stpcpy (p, ", "); p = stpcpy (p, mapf(i) ); } } if (p) strcpy (p, "\n" ); return list; } /* Set the file pointer into binary mode if required. */ static void set_binary (FILE *fp) { #ifdef HAVE_DOSISH_SYSTEM setmode (fileno (fp), O_BINARY); #else (void)fp; #endif } static void wrong_args (const char *text) { fprintf (stderr, _("usage: %s [options] %s\n"), GPGSM_NAME, text); gpgsm_exit (2); } 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)); } /* Setup the debugging. With a DEBUG_LEVEL of NULL only the active debug flags are propagated to the subsystems. With DEBUG_LEVEL set, a specific set of debug flags is set; and individual debugging flags will be added on top. */ static void set_debug (void) { int numok = (debug_level && digitp (debug_level)); int numlvl = numok? atoi (debug_level) : 0; if (!debug_level) ; else if (!strcmp (debug_level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_IPC_VALUE; else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) opt.debug = DBG_IPC_VALUE|DBG_X509_VALUE; else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE |DBG_CACHE_VALUE|DBG_CRYPTO_VALUE); else if (!strcmp (debug_level, "guru") || numok) { opt.debug = ~0; /* Unless the "guru" string has been used we don't want to allow hashing debugging. The rationale is that people tend to select the highest debug value and would then clutter their disk with debug files which may reveal confidential data. */ if (numok) opt.debug &= ~(DBG_HASHING_VALUE); } else { log_error (_("invalid debug-level '%s' given\n"), debug_level); gpgsm_exit (2); } opt.debug |= debug_value; if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug) opt.quiet = 0; if (opt.debug & DBG_MPI_VALUE) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); /* minip12.c may be used outside of GnuPG, thus we don't have the * opt structure over there. */ p12_set_verbosity (opt.verbose); } static void set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) { enum cmd_and_opt_values cmd = *ret_cmd; if (!cmd || cmd == new_cmd) cmd = new_cmd; else if ( cmd == aSign && new_cmd == aEncr ) cmd = aSignEncr; else if ( cmd == aEncr && new_cmd == aSign ) cmd = aSignEncr; else if ( (cmd == aSign && new_cmd == aClearsign) || (cmd == aClearsign && new_cmd == aSign) ) cmd = aClearsign; else { log_error(_("conflicting commands\n")); gpgsm_exit(2); } *ret_cmd = cmd; } /* Helper to add recipients to a list. */ static void do_add_recipient (ctrl_t ctrl, const char *name, certlist_t *recplist, int is_encrypt_to, int recp_required) { int rc = gpgsm_add_to_certlist (ctrl, name, 0, recplist, is_encrypt_to); if (rc) { if (recp_required) { log_error ("can't encrypt to '%s': %s\n", name, gpg_strerror (rc)); gpgsm_status2 (ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), name, NULL); } else log_info (_("Note: won't be able to encrypt to '%s': %s\n"), name, gpg_strerror (rc)); } } static void parse_validation_model (const char *model) { int i = gpgsm_parse_validation_model (model); if (i == -1) log_error (_("unknown validation model '%s'\n"), model); else default_validation_model = i; } int main ( int argc, char **argv) { gpg_error_t err = 0; gpgrt_argparse_t pargs; int orig_argc; char **orig_argv; /* char *username;*/ int may_coredump; strlist_t sl, remusr= NULL, locusr=NULL; strlist_t nrings=NULL; int detached_sig = 0; char *last_configname = NULL; const char *configname = NULL; /* NULL or points to last_configname. * NULL also indicates that we are * processing options from the cmdline. */ int debug_argparser = 0; int no_more_options = 0; int default_keyring = 1; char *logfile = NULL; char *auditlog = NULL; char *htmlauditlog = NULL; int greeting = 0; int nogreeting = 0; int debug_wait = 0; int use_random_seed = 1; int no_common_certs_import = 0; int with_fpr = 0; const char *forced_digest_algo = NULL; const char *extra_digest_algo = NULL; enum cmd_and_opt_values cmd = 0; struct server_control_s ctrl; certlist_t recplist = NULL; certlist_t signerlist = NULL; int do_not_setup_keys = 0; int recp_required = 0; estream_t auditfp = NULL; estream_t htmlauditfp = NULL; struct assuan_malloc_hooks malloc_hooks; int pwfd = -1; static const char *homedirvalue; static const char *changeuser; early_system_init (); gnupg_reopen_std (GPGSM_NAME); /* trap_unaligned ();*/ gnupg_rl_initialize (); gpgrt_set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); /* Please note that we may running SUID(ROOT), so be very CAREFUL when adding any stuff between here and the call to secmem_init() somewhere after the option parsing */ log_set_prefix (GPGSM_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); /* Check that the libraries are suitable. Do it here because the option parse may need services of the library */ if (!ksba_check_version (NEED_KSBA_VERSION) ) log_fatal (_("%s is too old (need %s, have %s)\n"), "libksba", NEED_KSBA_VERSION, ksba_check_version (NULL) ); gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); may_coredump = disable_core_dumps (); gnupg_init_signals (0, emergency_cleanup); dotlock_create (NULL, 0); /* Register lockfile cleanup. */ /* Tell the compliance module who we are. */ gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPGSM); opt.autostart = 1; opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); /* Note: If you change this default cipher algorithm , please remember to update the Gpgconflist entry as well. */ opt.def_cipher_algoid = DEFAULT_CIPHER_ALGO; /* First check whether we have a config file on the commandline */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oDebug: case oDebugAll: debug_argparser++; break; case oNoOptions: /* Set here here because the homedir would otherwise be * created before main option parsing starts. */ opt.no_homedir_creation = 1; break; case oHomedir: homedirvalue = pargs.r.ret_str; break; case oChUid: changeuser = pargs.r.ret_str; break; case aCallProtectTool: /* Make sure that --version and --help are passed to the * protect-tool. */ goto leave_cmdline_parser; } } leave_cmdline_parser: /* Reset the flags. */ pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); /* Initialize the secure memory. */ gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); maybe_setuid = 0; /* Now we are now working under our real uid */ ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Change UID and then set homedir. */ if (changeuser && gnupg_chuid (changeuser, 0)) log_inc_errorcount (); /* Force later termination. */ gnupg_set_homedir (homedirvalue); /* Setup a default control structure for command line mode */ memset (&ctrl, 0, sizeof ctrl); gpgsm_init_default_ctrl (&ctrl); ctrl.no_server = 1; ctrl.status_fd = -1; /* No status output. */ ctrl.autodetect_encoding = 1; /* Set the default policy file */ opt.policy_file = make_filename (gnupg_homedir (), "policies.txt", NULL); /* The configuraton directories for use by gpgrt_argparser. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); /* We are re-using the struct, thus the reset flag. We OR the * flags so that the internal intialized flag won't be cleared. */ argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags |= (ARGPARSE_FLAG_RESET | ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER); while (!no_more_options && gpgrt_argparser (&pargs, opts, GPGSM_NAME EXTSEP_S "conf")) { switch (pargs.r_opt) { case ARGPARSE_CONFFILE: if (debug_argparser) log_info (_("reading options from '%s'\n"), pargs.r_type? pargs.r.ret_str: "[cmdline]"); if (pargs.r_type) { xfree (last_configname); last_configname = xstrdup (pargs.r.ret_str); configname = last_configname; } else configname = NULL; break; case aGPGConfList: case aGPGConfTest: set_cmd (&cmd, pargs.r_opt); do_not_setup_keys = 1; default_keyring = 0; nogreeting = 1; break; case aServer: opt.batch = 1; set_cmd (&cmd, aServer); break; case aCallDirmngr: opt.batch = 1; set_cmd (&cmd, aCallDirmngr); do_not_setup_keys = 1; break; case aCallProtectTool: opt.batch = 1; set_cmd (&cmd, aCallProtectTool); no_more_options = 1; /* Stop parsing. */ do_not_setup_keys = 1; break; case aDeleteKey: set_cmd (&cmd, aDeleteKey); /*greeting=1;*/ do_not_setup_keys = 1; break; case aDetachedSign: detached_sig = 1; set_cmd (&cmd, aSign ); break; case aKeygen: set_cmd (&cmd, aKeygen); greeting=1; do_not_setup_keys = 1; break; case aImport: case aSendKeys: case aRecvKeys: case aExport: case aExportSecretKeyP12: case aExportSecretKeyP8: case aExportSecretKeyRaw: case aShowCerts: case aDumpKeys: case aDumpChain: case aDumpExternalKeys: case aDumpSecretKeys: case aListKeys: case aListExternalKeys: case aListSecretKeys: case aListChain: case aLearnCard: case aPasswd: case aKeydbClearSomeCertFlags: do_not_setup_keys = 1; set_cmd (&cmd, pargs.r_opt); break; case aEncr: recp_required = 1; set_cmd (&cmd, pargs.r_opt); break; case aSym: case aDecrypt: case aSign: case aClearsign: case aVerify: set_cmd (&cmd, pargs.r_opt); break; /* Output encoding selection. */ case oArmor: ctrl.create_pem = 1; break; case oBase64: ctrl.create_pem = 0; ctrl.create_base64 = 1; break; case oNoArmor: ctrl.create_pem = 0; ctrl.create_base64 = 0; break; case oP12Charset: opt.p12_charset = pargs.r.ret_str; break; case oPassphraseFD: pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0); break; case oPinentryMode: opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str); if (opt.pinentry_mode == -1) log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str); break; case oRequestOrigin: opt.request_origin = parse_request_origin (pargs.r.ret_str); if (opt.request_origin == -1) log_error (_("invalid request origin '%s'\n"), pargs.r.ret_str); break; /* Input encoding selection. */ case oAssumeArmor: ctrl.autodetect_encoding = 0; ctrl.is_pem = 1; ctrl.is_base64 = 0; break; case oAssumeBase64: ctrl.autodetect_encoding = 0; ctrl.is_pem = 0; ctrl.is_base64 = 1; break; case oAssumeBinary: ctrl.autodetect_encoding = 0; ctrl.is_pem = 0; ctrl.is_base64 = 0; break; case oDisableCRLChecks: opt.no_crl_check = 1; break; case oEnableCRLChecks: opt.no_crl_check = 0; break; case oDisableTrustedCertCRLCheck: opt.no_trusted_cert_crl_check = 1; break; case oEnableTrustedCertCRLCheck: opt.no_trusted_cert_crl_check = 0; break; case oForceCRLRefresh: opt.force_crl_refresh = 1; break; case oEnableIssuerBasedCRLCheck: opt.enable_issuer_based_crl_check = 1; break; case oDisableOCSP: ctrl.use_ocsp = opt.enable_ocsp = 0; break; case oEnableOCSP: ctrl.use_ocsp = opt.enable_ocsp = 1; break; case oIncludeCerts: ctrl.include_certs = default_include_certs = pargs.r.ret_int; break; case oPolicyFile: xfree (opt.policy_file); if (*pargs.r.ret_str) opt.policy_file = xstrdup (pargs.r.ret_str); else opt.policy_file = NULL; break; case oDisablePolicyChecks: opt.no_policy_check = 1; break; case oEnablePolicyChecks: opt.no_policy_check = 0; break; case oAutoIssuerKeyRetrieve: opt.auto_issuer_key_retrieve = 1; break; case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oNoTTY: /* fixme:tty_no_terminal(1);*/ break; case oDryRun: opt.dry_run = 1; break; case oVerbose: opt.verbose++; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oNoVerbose: opt.verbose = 0; gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); break; case oLogFile: logfile = pargs.r.ret_str; break; case oNoLogFile: logfile = NULL; break; case oAuditLog: auditlog = pargs.r.ret_str; break; case oHtmlAuditLog: htmlauditlog = pargs.r.ret_str; break; case oBatch: opt.batch = 1; greeting = 0; break; case oNoBatch: opt.batch = 0; break; case oAnswerYes: opt.answer_yes = 1; break; case oAnswerNo: opt.answer_no = 1; break; case oKeyring: append_to_strlist (&nrings, pargs.r.ret_str); break; case oUseKeyboxd: opt.use_keyboxd = 1; break; case oDebug: if (parse_debug_flag (pargs.r.ret_str, &debug_value, debug_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oDebugAll: debug_value = ~0; break; case oDebugNone: debug_value = 0; break; case oDebugLevel: debug_level = pargs.r.ret_str; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oDebugAllowCoreDump: may_coredump = enable_core_dumps (); break; case oDebugNoChainValidation: opt.no_chain_validation = 1; break; case oDebugIgnoreExpiration: opt.ignore_expiration = 1; break; case oDebugForceECDHSHA1KDF: opt.force_ecdh_sha1kdf = 1; break; case oCompatibilityFlags: if (parse_compatibility_flags (pargs.r.ret_str, &opt.compat_flags, compatibility_flags)) { pargs.r_opt = ARGPARSE_INVALID_ARG; pargs.err = ARGPARSE_PRINT_ERROR; } break; case oStatusFD: ctrl.status_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 1); break; case oLoggerFD: log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; case oWithMD5Fingerprint: opt.with_md5_fingerprint=1; /*fall through*/ case oWithFingerprint: with_fpr=1; /*fall through*/ case aFingerprint: opt.fingerprint++; break; case oWithKeygrip: opt.with_keygrip = 1; break; case oWithKeyScreening: opt.with_key_screening = 1; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oChUid: break; /* Command line only (see above). */ case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; case oKeyboxdProgram: opt.keyboxd_program = pargs.r.ret_str; 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 = xstrdup (pargs.r.ret_str); break; case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break; case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; case oDisableDirmngr: opt.disable_dirmngr = 1; break; case oPreferSystemDirmngr: /* Obsolete */; break; case oProtectToolProgram: opt.protect_tool_program = pargs.r.ret_str; break; case oFakedSystemTime: { time_t faked_time = isotime2epoch (pargs.r.ret_str); if (faked_time == (time_t)(-1)) faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); gnupg_set_time (faked_time, 0); } break; case oNoDefKeyring: default_keyring = 0; break; case oNoGreeting: nogreeting = 1; break; case oDefaultKey: if (*pargs.r.ret_str) { xfree (opt.local_user); opt.local_user = xstrdup (pargs.r.ret_str); } break; case oDefRecipient: if (*pargs.r.ret_str) opt.def_recipient = xstrdup (pargs.r.ret_str); break; case oDefRecipientSelf: xfree (opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 1; break; case oNoDefRecipient: xfree (opt.def_recipient); opt.def_recipient = NULL; opt.def_recipient_self = 0; break; case oWithKeyData: opt.with_key_data=1; /* fall through */ case oWithColons: ctrl.with_colons = 1; break; case oWithSecret: ctrl.with_secret = 1; break; case oWithValidation: ctrl.with_validation=1; break; case oWithEphemeralKeys: ctrl.with_ephemeral_keys=1; break; case oSkipVerify: opt.skip_verify=1; break; case oNoEncryptTo: opt.no_encrypt_to = 1; break; case oEncryptTo: /* Store the recipient in the second list */ sl = add_to_strlist (&remusr, pargs.r.ret_str); sl->flags = 1; break; case oRecipient: /* store the recipient */ add_to_strlist ( &remusr, pargs.r.ret_str); break; case oUser: /* Store the local users, the first one is the default */ if (!opt.local_user) opt.local_user = xstrdup (pargs.r.ret_str); add_to_strlist (&locusr, pargs.r.ret_str); break; case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break; case oCipherAlgo: opt.def_cipher_algoid = pargs.r.ret_str; break; case oDisableCipherAlgo: { int algo = gcry_cipher_map_name (pargs.r.ret_str); gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); } break; case oDisablePubkeyAlgo: { int algo = gcry_pk_map_name (pargs.r.ret_str); gcry_pk_ctl (GCRYCTL_DISABLE_ALGO,&algo, sizeof algo ); } break; case oDigestAlgo: forced_digest_algo = pargs.r.ret_str; break; case oExtraDigestAlgo: extra_digest_algo = pargs.r.ret_str; break; case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; case oNoRandomSeedFile: use_random_seed = 0; break; case oNoCommonCertsImport: no_common_certs_import = 1; break; case oEnableSpecialFilenames: enable_special_filenames (); break; case oValidationModel: parse_validation_model (pargs.r.ret_str); break; case oKeyServer: append_to_strlist (&opt.keyserver, pargs.r.ret_str); break; case oKeyServer_deprecated: obsolete_option (configname, pargs.lineno, "ldapserver"); break; case oIgnoreCertExtension: add_to_strlist (&opt.ignored_cert_extensions, pargs.r.ret_str); break; case oIgnoreCertWithOID: add_to_strlist (&opt.ignore_cert_with_oid, pargs.r.ret_str); break; case oAuthenticode: opt.authenticode = 1; break; case oAttribute: add_to_strlist (&opt.attributes, pargs.r.ret_str); break; case oNoAutostart: opt.autostart = 0; break; case oCompliance: { struct gnupg_compliance_option compliance_options[] = { { "gnupg", CO_GNUPG }, { "de-vs", CO_DE_VS } }; int compliance = gnupg_parse_compliance_option (pargs.r.ret_str, compliance_options, DIM (compliance_options), opt.quiet); if (compliance < 0) log_inc_errorcount (); /* Force later termination. */ opt.compliance = compliance; } break; case oMinRSALength: opt.min_rsa_length = pargs.r.ret_ulong; break; case oRequireCompliance: opt.require_compliance = 1; break; default: if (configname) pargs.err = ARGPARSE_PRINT_WARNING; else { pargs.err = ARGPARSE_PRINT_ERROR; /* The argparse function calls a plain exit and thus we * need to print a status here. */ gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser", gpg_error (GPG_ERR_GENERAL)); } break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (!last_configname) opt.config_filename = gpgrt_fnameconcat (gnupg_homedir (), GPGSM_NAME EXTSEP_S "conf", NULL); else opt.config_filename = last_configname; if (log_get_errorcount(0)) { gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser", gpg_error (GPG_ERR_GENERAL)); gpgsm_exit(2); } /* Process common component options. */ if (parse_comopt (GNUPG_MODULE_NAME_GPGSM, debug_argparser)) { gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser", gpg_error (GPG_ERR_GENERAL)); gpgsm_exit(2); } if (opt.use_keyboxd) log_info ("Note: Please move option \"%s\" to \"common.conf\"\n", "use-keyboxd"); opt.use_keyboxd = comopt.use_keyboxd; /* Override. */ if (opt.keyboxd_program) log_info ("Note: Please move option \"%s\" to \"common.conf\"\n", "keyboxd-program"); if (!opt.keyboxd_program && comopt.keyboxd_program) { opt.keyboxd_program = comopt.keyboxd_program; comopt.keyboxd_program = NULL; } if (comopt.no_autostart) opt.autostart = 0; if (pwfd != -1) /* Read the passphrase now. */ read_passphrase_from_fd (pwfd); /* Now that we have the options parsed we need to update the default control structure. */ gpgsm_init_default_ctrl (&ctrl); if (nogreeting) greeting = 0; if (greeting) { es_fprintf (es_stderr, "%s %s; %s\n", gpgrt_strusage(11), gpgrt_strusage(13), gpgrt_strusage(14) ); es_fprintf (es_stderr, "%s\n", gpgrt_strusage(15) ); } #ifdef IS_DEVELOPMENT_VERSION if (!opt.batch) { log_info ("NOTE: THIS IS A DEVELOPMENT VERSION!\n"); log_info ("It is only intended for test purposes and should NOT be\n"); log_info ("used in a production environment or with production keys!\n"); } #endif if (may_coredump && !opt.quiet) log_info (_("WARNING: program may create a core file!\n")); npth_init (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); /* if (opt.qualsig_approval && !opt.quiet) */ /* log_info (_("This software has officially been approved to " */ /* "create and verify\n" */ /* "qualified signatures according to German law.\n")); */ if (logfile && cmd == aServer) { log_set_file (logfile); log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); } if (gnupg_faked_time_p ()) { gnupg_isotime_t tbuf; log_info (_("WARNING: running with faked system time: ")); gnupg_get_isotime (tbuf); dump_isotime (tbuf); log_printf ("\n"); } /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("Note: '%s' is not considered an option\n"), argv[i]); } /*FIXME if (opt.batch) */ /* tty_batchmode (1); */ gcry_control (GCRYCTL_RESUME_SECMEM_WARN); set_debug (); if (opt.verbose) /* Print the compatibility flags. */ parse_compatibility_flags (NULL, &opt.compat_flags, compatibility_flags); gnupg_set_compliance_extra_info (opt.min_rsa_length); /* Although we always use gpgsm_exit, we better install a regular exit handler so that at least the secure memory gets wiped out. */ if (atexit (emergency_cleanup)) { log_error ("atexit failed\n"); gpgsm_exit (2); } /* Must do this after dropping setuid, because the mapping functions may try to load an module and we may have disabled an algorithm. We remap the commonly used algorithms to the OIDs for convenience. We need to work with the OIDs because they are used to check whether the encryption mode is actually available. */ if (!strcmp (opt.def_cipher_algoid, "3DES") ) opt.def_cipher_algoid = "1.2.840.113549.3.7"; else if (!strcmp (opt.def_cipher_algoid, "AES") || !strcmp (opt.def_cipher_algoid, "AES128")) opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.2"; else if (!strcmp (opt.def_cipher_algoid, "AES192") ) opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.22"; else if (!strcmp (opt.def_cipher_algoid, "AES256") ) opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.42"; else if (!strcmp (opt.def_cipher_algoid, "SERPENT") || !strcmp (opt.def_cipher_algoid, "SERPENT128") ) opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.2"; else if (!strcmp (opt.def_cipher_algoid, "SERPENT192") ) opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.22"; else if (!strcmp (opt.def_cipher_algoid, "SERPENT256") ) opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.42"; else if (!strcmp (opt.def_cipher_algoid, "SEED") ) opt.def_cipher_algoid = "1.2.410.200004.1.4"; else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA") || !strcmp (opt.def_cipher_algoid, "CAMELLIA128") ) opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.2"; else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA192") ) opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.3"; else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA256") ) opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.4"; if (cmd != aGPGConfList) { if ( !gcry_cipher_map_name (opt.def_cipher_algoid) || !gcry_cipher_mode_from_oid (opt.def_cipher_algoid)) log_error (_("selected cipher algorithm is invalid\n")); if (forced_digest_algo) { opt.forced_digest_algo = gcry_md_map_name (forced_digest_algo); if (our_md_test_algo(opt.forced_digest_algo) ) log_error (_("selected digest algorithm is invalid\n")); } if (extra_digest_algo) { opt.extra_digest_algo = gcry_md_map_name (extra_digest_algo); if (our_md_test_algo (opt.extra_digest_algo) ) log_error (_("selected digest algorithm is invalid\n")); } } /* Check our chosen algorithms against the list of allowed * algorithms in the current compliance mode, and fail hard if it is * not. This is us being nice to the user informing her early that * the chosen algorithms are not available. We also check and * enforce this right before the actual operation. */ if (! gnupg_cipher_is_allowed (opt.compliance, cmd == aEncr || cmd == aSignEncr, gcry_cipher_map_name (opt.def_cipher_algoid), GCRY_CIPHER_MODE_NONE) && ! gnupg_cipher_is_allowed (opt.compliance, cmd == aEncr || cmd == aSignEncr, gcry_cipher_mode_from_oid (opt.def_cipher_algoid), GCRY_CIPHER_MODE_NONE)) log_error (_("cipher algorithm '%s' may not be used in %s mode\n"), opt.def_cipher_algoid, gnupg_compliance_option_string (opt.compliance)); if (forced_digest_algo && ! gnupg_digest_is_allowed (opt.compliance, cmd == aSign || cmd == aSignEncr || cmd == aClearsign, opt.forced_digest_algo)) log_error (_("digest algorithm '%s' may not be used in %s mode\n"), forced_digest_algo, gnupg_compliance_option_string (opt.compliance)); if (extra_digest_algo && ! gnupg_digest_is_allowed (opt.compliance, cmd == aSign || cmd == aSignEncr || cmd == aClearsign, opt.extra_digest_algo)) log_error (_("digest algorithm '%s' may not be used in %s mode\n"), extra_digest_algo, gnupg_compliance_option_string (opt.compliance)); if (log_get_errorcount(0)) { gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-postprocessing", gpg_error (GPG_ERR_GENERAL)); gpgsm_exit (2); } /* Set the random seed file. */ if (use_random_seed) { char *p = make_filename (gnupg_homedir (), "random_seed", NULL); gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); xfree(p); } if (!cmd && opt.fingerprint && !with_fpr) set_cmd (&cmd, aListKeys); /* If no pinentry is expected shunt * gnupg_allow_set_foregound_window to avoid useless error * messages on Windows. */ if (opt.pinentry_mode != PINENTRY_MODE_ASK) { gnupg_inhibit_set_foregound_window (1); } /* Add default keybox. */ if (!nrings && default_keyring && !opt.use_keyboxd) { int created; keydb_add_resource (&ctrl, "pubring.kbx", 0, &created); if (created && !no_common_certs_import) { /* Import the standard certificates for a new default keybox. */ char *filelist[2]; filelist[0] = make_filename (gnupg_datadir (),"com-certs.pem", NULL); filelist[1] = NULL; if (!gnupg_access (filelist[0], F_OK)) { log_info (_("importing common certificates '%s'\n"), filelist[0]); gpgsm_import_files (&ctrl, 1, filelist, open_read); } xfree (filelist[0]); } } if (!opt.use_keyboxd) { for (sl = nrings; sl; sl = sl->next) keydb_add_resource (&ctrl, sl->d, 0, NULL); } FREE_STRLIST(nrings); /* Prepare the audit log feature for certain commands. */ if (auditlog || htmlauditlog) { switch (cmd) { case aEncr: case aSign: case aDecrypt: case aVerify: audit_release (ctrl.audit); ctrl.audit = audit_new (); if (auditlog) auditfp = open_es_fwrite (auditlog); if (htmlauditlog) htmlauditfp = open_es_fwrite (htmlauditlog); break; default: break; } } if (!do_not_setup_keys) { int errcount = log_get_errorcount (0); for (sl = locusr; sl ; sl = sl->next) { int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 1, &signerlist, 0); if (rc) { log_error (_("can't sign using '%s': %s\n"), sl->d, gpg_strerror (rc)); gpgsm_status2 (&ctrl, STATUS_INV_SGNR, get_inv_recpsgnr_code (rc), sl->d, NULL); gpgsm_status2 (&ctrl, STATUS_INV_RECP, get_inv_recpsgnr_code (rc), sl->d, NULL); } } /* Build the recipient list. We first add the regular ones and then the encrypt-to ones because the underlying function will silently ignore duplicates and we can't allow keeping a duplicate which is flagged as encrypt-to as the actually encrypt function would then complain about no (regular) recipients. */ for (sl = remusr; sl; sl = sl->next) if (!(sl->flags & 1)) do_add_recipient (&ctrl, sl->d, &recplist, 0, recp_required); if (!opt.no_encrypt_to) { for (sl = remusr; sl; sl = sl->next) if ((sl->flags & 1)) do_add_recipient (&ctrl, sl->d, &recplist, 1, recp_required); } /* We do not require a recipient for decryption but because * recipients and signers are always checked and log_error is * sometimes used (for failed signing keys or due to a failed * CRL checking) that would have bumbed up the error counter. * We clear the counter in the decryption case because there is * no reason to force decryption to fail. */ if (cmd == aDecrypt && !errcount) log_get_errorcount (1); /* clear counter */ } if (log_get_errorcount(0)) gpgsm_exit(1); /* Must stop for invalid recipients. */ /* Dispatch command. */ switch (cmd) { case aGPGConfList: { /* List default option values in the GPG Conf format. */ es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT); es_printf ("include-certs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, DEFAULT_INCLUDE_CERTS); es_printf ("cipher-algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, DEFAULT_CIPHER_ALGO); es_printf ("p12-charset:%lu:\n", GC_OPT_FLAG_DEFAULT); es_printf ("default-key:%lu:\n", GC_OPT_FLAG_DEFAULT); es_printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_DEFAULT); /* The next one is an info only item and should match what proc_parameters actually implements. */ es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "RSA-3072"); } break; case aGPGConfTest: /* This is merely a dummy command to test whether the configuration file is valid. */ break; case aServer: if (debug_wait) { log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } gpgsm_server (recplist); break; case aCallDirmngr: if (!argc) wrong_args ("--call-dirmngr {args}"); else if (gpgsm_dirmngr_run_command (&ctrl, *argv, argc-1, argv+1)) gpgsm_exit (1); break; case aCallProtectTool: run_protect_tool (argc, argv); break; case aEncr: /* Encrypt the given file. */ { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); set_binary (stdin); if (!argc) /* Source is stdin. */ err = gpgsm_encrypt (&ctrl, recplist, 0, fp); else if (argc == 1) /* Source is the given file. */ err = gpgsm_encrypt (&ctrl, recplist, open_read (*argv), fp); else wrong_args ("--encrypt [datafile]"); if (err) gpgrt_fcancel (fp); else es_fclose (fp); } break; case aSign: /* Sign the given file. */ { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); /* Fixme: We should also allow concatenation of multiple files for signing because that is what gpg does.*/ set_binary (stdin); if (!argc) /* Create from stdin. */ err = gpgsm_sign (&ctrl, signerlist, 0, detached_sig, fp); else if (argc == 1) /* From file. */ err = gpgsm_sign (&ctrl, signerlist, open_read (*argv), detached_sig, fp); else wrong_args ("--sign [datafile]"); #if GPGRT_VERSION_NUMBER >= 0x012700 /* >= 1.39 */ if (err) gpgrt_fcancel (fp); else es_fclose (fp); #else (void)err; es_fclose (fp); #endif } break; case aSignEncr: /* sign and encrypt the given file */ log_error ("this command has not yet been implemented\n"); break; case aClearsign: /* make a clearsig */ log_error ("this command has not yet been implemented\n"); break; case aVerify: { estream_t fp = NULL; set_binary (stdin); if (argc == 2 && opt.outfile) log_info ("option --output ignored for a detached signature\n"); else if (opt.outfile) fp = open_es_fwrite (opt.outfile); if (!argc) gpgsm_verify (&ctrl, 0, -1, fp); /* normal signature from stdin */ else if (argc == 1) gpgsm_verify (&ctrl, open_read (*argv), -1, fp); /* std signature */ else if (argc == 2) /* detached signature (sig, detached) */ gpgsm_verify (&ctrl, open_read (*argv), open_read (argv[1]), NULL); else wrong_args ("--verify [signature [detached_data]]"); es_fclose (fp); } break; case aDecrypt: { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); set_binary (stdin); if (!argc) err = gpgsm_decrypt (&ctrl, 0, fp); /* from stdin */ else if (argc == 1) err = gpgsm_decrypt (&ctrl, open_read (*argv), fp); /* from file */ else wrong_args ("--decrypt [filename]"); if (err) gpgrt_fcancel (fp); else es_fclose (fp); } break; case aDeleteKey: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_delete (&ctrl, sl); free_strlist(sl); break; case aListChain: case aDumpChain: ctrl.with_chain = 1; /* fall through */ case aListKeys: case aDumpKeys: case aListExternalKeys: case aDumpExternalKeys: case aListSecretKeys: case aDumpSecretKeys: { unsigned int mode; estream_t fp; switch (cmd) { case aListChain: case aListKeys: mode = (0 | 0 | (1<<6)); break; case aDumpChain: case aDumpKeys: mode = (256 | 0 | (1<<6)); break; case aListExternalKeys: mode = (0 | 0 | (1<<7)); break; case aDumpExternalKeys: mode = (256 | 0 | (1<<7)); break; case aListSecretKeys: mode = (0 | 2 | (1<<6)); break; case aDumpSecretKeys: mode = (256 | 2 | (1<<6)); break; default: BUG(); } fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_list_keys (&ctrl, sl, fp, mode); free_strlist(sl); es_fclose (fp); } break; case aShowCerts: { estream_t fp; fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); gpgsm_show_certs (&ctrl, argc, argv, fp); es_fclose (fp); } break; case aKeygen: /* Generate a key; well kind of. */ { estream_t fpin = NULL; estream_t fpout; if (opt.batch) { if (!argc) /* Create from stdin. */ fpin = open_es_fread ("-", "r"); else if (argc == 1) /* From file. */ fpin = open_es_fread (*argv, "r"); else wrong_args ("--generate-key --batch [parmfile]"); } fpout = open_es_fwrite (opt.outfile?opt.outfile:"-"); if (fpin) gpgsm_genkey (&ctrl, fpin, fpout); else gpgsm_gencertreq_tty (&ctrl, fpout); es_fclose (fpout); } break; case aImport: gpgsm_import_files (&ctrl, argc, argv, open_read); break; case aExport: { estream_t fp; fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); gpgsm_export (&ctrl, sl, fp); free_strlist(sl); es_fclose (fp); } break; case aExportSecretKeyP12: { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); if (argc == 1) gpgsm_p12_export (&ctrl, *argv, fp, 0); else wrong_args ("--export-secret-key-p12 KEY-ID"); if (fp != es_stdout) es_fclose (fp); } break; case aExportSecretKeyP8: { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); if (argc == 1) gpgsm_p12_export (&ctrl, *argv, fp, 1); else wrong_args ("--export-secret-key-p8 KEY-ID"); if (fp != es_stdout) es_fclose (fp); } break; case aExportSecretKeyRaw: { estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-"); if (argc == 1) gpgsm_p12_export (&ctrl, *argv, fp, 2); else wrong_args ("--export-secret-key-raw KEY-ID"); if (fp != es_stdout) es_fclose (fp); } break; case aSendKeys: case aRecvKeys: log_error ("this command has not yet been implemented\n"); break; case aLearnCard: if (argc) wrong_args ("--learn-card"); else { int rc = gpgsm_agent_learn (&ctrl); if (rc) log_error ("error learning card: %s\n", gpg_strerror (rc)); } break; case aPasswd: if (argc != 1) wrong_args ("--change-passphrase "); else { int rc; ksba_cert_t cert = NULL; char *grip = NULL; rc = gpgsm_find_cert (&ctrl, *argv, NULL, &cert, 0); if (rc) ; else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) rc = gpg_error (GPG_ERR_BUG); else { char *desc = gpgsm_format_keydesc (cert); rc = gpgsm_agent_passwd (&ctrl, grip, desc); xfree (desc); } if (rc) log_error ("error changing passphrase: %s\n", gpg_strerror (rc)); xfree (grip); ksba_cert_release (cert); } break; case aKeydbClearSomeCertFlags: for (sl=NULL; argc; argc--, argv++) add_to_strlist (&sl, *argv); keydb_clear_some_cert_flags (&ctrl, sl); free_strlist(sl); break; default: log_error (_("invalid command (there is no implicit command)\n")); break; } /* Print the audit result if needed. */ if ((auditlog && auditfp) || (htmlauditlog && htmlauditfp)) { if (auditlog && auditfp) audit_print_result (ctrl.audit, auditfp, 0); if (htmlauditlog && htmlauditfp) audit_print_result (ctrl.audit, htmlauditfp, 1); audit_release (ctrl.audit); ctrl.audit = NULL; es_fclose (auditfp); es_fclose (htmlauditfp); } /* cleanup */ gpgsm_deinit_default_ctrl (&ctrl); free_strlist (opt.keyserver); opt.keyserver = NULL; gpgsm_release_certlist (recplist); gpgsm_release_certlist (signerlist); FREE_STRLIST (remusr); FREE_STRLIST (locusr); gpgsm_exit(0); return 8; /*NOTREACHED*/ } /* Note: This function is used by signal handlers!. */ static void emergency_cleanup (void) { gcry_control (GCRYCTL_TERM_SECMEM ); } void gpgsm_exit (int rc) { gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); if (opt.debug & DBG_MEMSTAT_VALUE) { gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); } if (opt.debug) gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); emergency_cleanup (); rc = rc? rc : log_get_errorcount(0)? 2 : gpgsm_errors_seen? 1 : 0; exit (rc); } void gpgsm_init_default_ctrl (struct server_control_s *ctrl) { ctrl->include_certs = default_include_certs; ctrl->use_ocsp = opt.enable_ocsp; ctrl->validation_model = default_validation_model; ctrl->offline = opt.disable_dirmngr; + ctrl->revoked_at[0] = 0; + ctrl->revocation_reason = NULL; } /* This function is called to deinitialize a control object. The * control object is is not released, though. */ void gpgsm_deinit_default_ctrl (ctrl_t ctrl) { gpgsm_keydb_deinit_session_data (ctrl); + xfree (ctrl->revocation_reason); + ctrl->revocation_reason = NULL; } int gpgsm_parse_validation_model (const char *model) { if (!ascii_strcasecmp (model, "shell") ) return 0; else if ( !ascii_strcasecmp (model, "chain") ) return 1; else if ( !ascii_strcasecmp (model, "steed") ) return 2; else return -1; } /* Open the FILENAME for read and return the file descriptor. Stop with an error message in case of problems. "-" denotes stdin and if special filenames are allowed the given fd is opened instead. */ static int open_read (const char *filename) { int fd; if (filename[0] == '-' && !filename[1]) { set_binary (stdin); return 0; /* stdin */ } fd = check_special_filename (filename, 0, 0); if (fd != -1) return fd; fd = gnupg_open (filename, O_RDONLY | O_BINARY, 0); if (fd == -1) { log_error (_("can't open '%s': %s\n"), filename, strerror (errno)); gpgsm_exit (2); } return fd; } /* Same as open_read but return an estream_t. */ static estream_t open_es_fread (const char *filename, const char *mode) { int fd; estream_t fp; if (filename[0] == '-' && !filename[1]) fd = fileno (stdin); else fd = check_special_filename (filename, 0, 0); if (fd != -1) { fp = es_fdopen_nc (fd, mode); if (!fp) { log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno)); gpgsm_exit (2); } return fp; } fp = es_fopen (filename, mode); if (!fp) { log_error (_("can't open '%s': %s\n"), filename, strerror (errno)); gpgsm_exit (2); } return fp; } /* Open FILENAME for fwrite and return an extended stream. Stop with an error message in case of problems. "-" denotes stdout and if special filenames are allowed the given fd is opened instead. Caller must close the returned stream. */ static estream_t open_es_fwrite (const char *filename) { int fd; estream_t fp; if (filename[0] == '-' && !filename[1]) { fflush (stdout); fp = es_fdopen_nc (fileno(stdout), "wb"); return fp; } fd = check_special_filename (filename, 1, 0); if (fd != -1) { fp = es_fdopen_nc (fd, "wb"); if (!fp) { log_error ("es_fdopen(%d) failed: %s\n", fd, strerror (errno)); gpgsm_exit (2); } return fp; } fp = es_fopen (filename, "wb"); if (!fp) { log_error (_("can't open '%s': %s\n"), filename, strerror (errno)); gpgsm_exit (2); } return fp; } static void run_protect_tool (int argc, char **argv) { #ifdef HAVE_W32_SYSTEM (void)argc; (void)argv; #else const char *pgm; char **av; int i; if (!opt.protect_tool_program || !*opt.protect_tool_program) pgm = gnupg_module_name (GNUPG_MODULE_NAME_PROTECT_TOOL); else pgm = opt.protect_tool_program; av = xcalloc (argc+2, sizeof *av); av[0] = strrchr (pgm, '/'); if (!av[0]) av[0] = xstrdup (pgm); for (i=1; argc; i++, argc--, argv++) av[i] = *argv; av[i] = NULL; execv (pgm, av); log_error ("error executing '%s': %s\n", pgm, strerror (errno)); #endif /*!HAVE_W32_SYSTEM*/ gpgsm_exit (2); } diff --git a/sm/gpgsm.h b/sm/gpgsm.h index b0ed8891c..ced2d679f 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -1,520 +1,526 @@ /* gpgsm.h - Global definitions for GpgSM * Copyright (C) 2001, 2003, 2004, 2007, 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 . */ #ifndef GPGSM_H #define GPGSM_H #ifdef GPG_ERR_SOURCE_DEFAULT #error GPG_ERR_SOURCE_DEFAULT already defined #endif #define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGSM #include #include #include "../common/util.h" #include "../common/status.h" #include "../common/audit.h" #include "../common/session-env.h" #include "../common/ksba-io-support.h" #include "../common/compliance.h" /* The maximum length of a binary fingerprints. This is used to * provide a static buffer and will be increased if we need to support * longer fingerprints. */ #define MAX_FINGERPRINT_LEN 32 /* The maximum length of a binary digest. */ #define MAX_DIGEST_LEN 64 /* Fits for SHA-512 */ /* A large struct named "opt" to keep global flags. */ EXTERN_UNLESS_MAIN_MODULE struct { unsigned int debug; /* debug flags (DBG_foo_VALUE) */ int verbose; /* verbosity level */ int quiet; /* be as quiet as possible */ int batch; /* run in batch mode, i.e w/o any user interaction */ int answer_yes; /* assume yes on most questions */ int answer_no; /* assume no on most questions */ int dry_run; /* don't change any persistent data */ int no_homedir_creation; int use_keyboxd; /* Use the external keyboxd as storage backend. */ const char *config_filename; /* Name of the used config file. */ const char *agent_program; const char *keyboxd_program; session_env_t session_env; char *lc_ctype; char *lc_messages; int autostart; const char *dirmngr_program; int disable_dirmngr; /* Do not do any dirmngr calls. */ const char *protect_tool_program; char *outfile; /* name of output file */ int with_key_data;/* include raw key in the column delimited output */ int fingerprint; /* list fingerprints in all key listings */ int with_md5_fingerprint; /* Also print an MD5 fingerprint for standard key listings. */ int with_keygrip; /* Option --with-keygrip active. */ int with_key_screening; /* Option --with-key-screening active. */ int pinentry_mode; int request_origin; int armor; /* force base64 armoring (see also ctrl.with_base64) */ int no_armor; /* don't try to figure out whether data is base64 armored*/ const char *p12_charset; /* Use this charset for encoding the pkcs#12 passphrase. */ const char *def_cipher_algoid; /* cipher algorithm to use if nothing else is specified */ int def_compress_algo; /* Ditto for compress algorithm */ int forced_digest_algo; /* User forced hash algorithm. */ int force_ecdh_sha1kdf; /* Only for debugging and testing. */ char *def_recipient; /* userID of the default recipient */ int def_recipient_self; /* The default recipient is the default key */ int no_encrypt_to; /* Ignore all as encrypt to marked recipients. */ char *local_user; /* NULL or argument to -u */ int extra_digest_algo; /* A digest algorithm also used for verification of signatures. */ int always_trust; /* Trust the given keys even if there is no valid certification chain */ int skip_verify; /* do not check signatures on data */ int lock_once; /* Keep lock once they are set */ int ignore_time_conflict; /* Ignore certain time conflicts */ int no_crl_check; /* Don't do a CRL check */ int no_trusted_cert_crl_check; /* Don't run a CRL check for trusted certs. */ int force_crl_refresh; /* Force refreshing the CRL. */ int enable_issuer_based_crl_check; /* Backward compatibility hack. */ int enable_ocsp; /* Default to use OCSP checks. */ char *policy_file; /* full pathname of policy file */ int no_policy_check; /* ignore certificate policies */ int no_chain_validation; /* Bypass all cert chain validity tests */ int ignore_expiration; /* Ignore the notAfter validity checks. */ int auto_issuer_key_retrieve; /* try to retrieve a missing issuer key. */ int qualsig_approval; /* Set to true if this software has officially been approved to create an verify qualified signatures. This is a runtime option in case we want to check the integrity of the software at runtime. */ unsigned int min_rsa_length; /* Used for compliance checks. */ strlist_t keyserver; /* A list of certificate extension OIDs which are ignored so that one can claim that a critical extension has been handled. One OID per string. */ strlist_t ignored_cert_extensions; /* A list of OIDs which will be used to ignore certificates with * sunch an OID during --learn-card. */ strlist_t ignore_cert_with_oid; /* The current compliance mode. */ enum gnupg_compliance_mode compliance; /* Fail if an operation can't be done in the requested compliance * mode. */ int require_compliance; /* Enable creation of authenticode signatures. */ int authenticode; /* A list of extra attributes put into a signed data object. For a * signed each attribute each string has the format: * :s: * and for an unsigned attribute * :u: * The OID is in the usual dotted decimal for. The HEX_OR_FILENAME * is either a list of hex digits or a filename with the DER encoded * value. A filename is detected by the presence of a slash in the * HEX_OR_FILENAME. The actual value needs to be encoded as a SET OF * attribute values. */ strlist_t attributes; /* Compatibility flags (COMPAT_FLAG_xxxx). */ unsigned int compat_flags; } opt; /* Debug values and macros. */ #define DBG_X509_VALUE 1 /* debug x.509 data reading/writing */ #define DBG_MPI_VALUE 2 /* debug mpi details */ #define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ #define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ #define DBG_CACHE_VALUE 64 /* debug the caching */ #define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ #define DBG_HASHING_VALUE 512 /* debug hashing operations */ #define DBG_IPC_VALUE 1024 /* debug assuan communication */ #define DBG_CLOCK_VALUE 4096 #define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */ #define DBG_X509 (opt.debug & DBG_X509_VALUE) #define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) #define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) #define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) #define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) #define DBG_IPC (opt.debug & DBG_IPC_VALUE) #define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE) #define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE) /* Compatibility flags */ /* Telesec RSA cards produced for NRW in 2022 came with only the * keyAgreement bit set. This flag allows there use for encryption * anyway. Example cert: * Issuer: /CN=DOI CA 10a/OU=DOI/O=PKI-1-Verwaltung/C=DE * key usage: digitalSignature nonRepudiation keyAgreement * policies: 1.3.6.1.4.1.7924.1.1:N: */ #define COMPAT_ALLOW_KA_TO_ENCR 1 /* Forward declaration for an object defined in server.c */ struct server_local_s; /* Object used to keep state locally in keydb.c */ struct keydb_local_s; typedef struct keydb_local_s *keydb_local_t; /* Session control object. This object is passed down to most functions. Note that the default values for it are set by gpgsm_init_default_ctrl(). */ struct server_control_s { int no_server; /* We are not running under server control */ int status_fd; /* Only for non-server mode */ struct server_local_s *server_local; keydb_local_t keydb_local; /* Local data for call-keyboxd.c */ audit_ctx_t audit; /* NULL or a context for the audit subsystem. */ int agent_seen; /* Flag indicating that the gpg-agent has been accessed. */ int with_colons; /* Use column delimited output format */ int with_secret; /* Mark secret keys in a public key listing. */ int with_chain; /* Include the certifying certs in a listing */ int with_validation;/* Validate each key while listing. */ int with_ephemeral_keys; /* Include ephemeral flagged keys in the keylisting. */ int autodetect_encoding; /* Try to detect the input encoding */ int is_pem; /* Is in PEM format */ int is_base64; /* is in plain base-64 format */ int create_base64; /* Create base64 encoded output */ int create_pem; /* create PEM output */ const char *pem_name; /* PEM name to use */ int include_certs; /* -1 to send all certificates in the chain along with a signature or the number of certificates up the chain (0 = none, 1 = only signer) */ int use_ocsp; /* Set to true if OCSP should be used. */ int validation_model; /* 0 := standard model (shell), 1 := chain model, 2 := STEED model. */ int offline; /* If true gpgsm won't do any network access. */ /* The current time. Used as a helper in certchain.c. */ ksba_isotime_t current_time; + + /* The revocation info. Used as a helper inc ertchain.c */ + gnupg_isotime_t revoked_at; + char *revocation_reason; }; /* An object to keep a list of certificates. */ struct certlist_s { struct certlist_s *next; ksba_cert_t cert; int is_encrypt_to; /* True if the certificate has been set through the --encrypto-to option. */ int pk_algo; /* The PK_ALGO from CERT or 0 if not yet known. */ int hash_algo; /* Used to track the hash algorithm to use. */ const char *hash_algo_oid; /* And the corresponding OID. */ }; typedef struct certlist_s *certlist_t; /* A structure carrying information about trusted root certificates. */ struct rootca_flags_s { unsigned int valid:1; /* The rest of the structure has valid information. */ unsigned int relax:1; /* Relax checking of root certificates. */ unsigned int chain_model:1; /* Root requires the use of the chain model. */ unsigned int qualified:1; /* Root CA used for qualfied signatures. */ }; /*-- gpgsm.c --*/ extern int gpgsm_errors_seen; void gpgsm_exit (int rc); void gpgsm_init_default_ctrl (struct server_control_s *ctrl); void gpgsm_deinit_default_ctrl (ctrl_t ctrl); int gpgsm_parse_validation_model (const char *model); /*-- server.c --*/ void gpgsm_server (certlist_t default_recplist); gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text); gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...) GPGRT_ATTR_SENTINEL(0); gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text, gpg_err_code_t ec); gpg_error_t gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text, gpg_error_t err); gpg_error_t gpgsm_proxy_pinentry_notify (ctrl_t ctrl, const unsigned char *line); /*-- fingerprint --*/ unsigned char *gpgsm_get_fingerprint (ksba_cert_t cert, int algo, unsigned char *array, int *r_len); char *gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo); char *gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo); unsigned long gpgsm_get_short_fingerprint (ksba_cert_t cert, unsigned long *r_high); unsigned char *gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array); char *gpgsm_get_keygrip_hexstring (ksba_cert_t cert); int gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits); int gpgsm_get_key_algo_info2 (ksba_cert_t cert, unsigned int *nbits, char **r_curve); int gpgsm_is_ecc_key (ksba_cert_t cert); char *gpgsm_pubkey_algo_string (ksba_cert_t cert, int *r_algoid); gcry_mpi_t gpgsm_get_rsa_modulus (ksba_cert_t cert); char *gpgsm_get_certid (ksba_cert_t cert); /*-- certdump.c --*/ const void *gpgsm_get_serial (ksba_const_sexp_t sn, size_t *r_length); void gpgsm_print_serial (estream_t fp, ksba_const_sexp_t p); void gpgsm_print_serial_decimal (estream_t fp, ksba_const_sexp_t sn); void gpgsm_print_time (estream_t fp, ksba_isotime_t t); void gpgsm_print_name2 (FILE *fp, const char *string, int translate); void gpgsm_print_name (FILE *fp, const char *string); void gpgsm_es_print_name (estream_t fp, const char *string); void gpgsm_es_print_name2 (estream_t fp, const char *string, int translate); void gpgsm_cert_log_name (const char *text, ksba_cert_t cert); void gpgsm_dump_cert (const char *text, ksba_cert_t cert); void gpgsm_dump_serial (ksba_const_sexp_t p); void gpgsm_dump_time (ksba_isotime_t t); void gpgsm_dump_string (const char *string); char *gpgsm_format_serial (ksba_const_sexp_t p); char *gpgsm_format_name2 (const char *name, int translate); char *gpgsm_format_name (const char *name); char *gpgsm_format_sn_issuer (ksba_sexp_t sn, const char *issuer); char *gpgsm_fpr_and_name_for_status (ksba_cert_t cert); char *gpgsm_format_keydesc (ksba_cert_t cert); /*-- certcheck.c --*/ int gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert); int gpgsm_check_cms_signature (ksba_cert_t cert, gcry_sexp_t sigval, gcry_md_hd_t md, int hash_algo, unsigned int pkalgoflags, int *r_pkalgo); /* fixme: move create functions to another file */ int gpgsm_create_cms_signature (ctrl_t ctrl, ksba_cert_t cert, gcry_md_hd_t md, int mdalgo, unsigned char **r_sigval); /*-- certchain.c --*/ /* Flags used with gpgsm_validate_chain. */ #define VALIDATE_FLAG_NO_DIRMNGR 1 #define VALIDATE_FLAG_CHAIN_MODEL 2 #define VALIDATE_FLAG_STEED 4 gpg_error_t gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next); int gpgsm_is_root_cert (ksba_cert_t cert); int gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime, ksba_isotime_t r_exptime, int listmode, estream_t listfp, unsigned int flags, unsigned int *retflags); int gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert); /*-- certlist.c --*/ int gpgsm_cert_use_sign_p (ksba_cert_t cert, int silent); int gpgsm_cert_use_encrypt_p (ksba_cert_t cert); int gpgsm_cert_use_verify_p (ksba_cert_t cert); int gpgsm_cert_use_decrypt_p (ksba_cert_t cert); int gpgsm_cert_use_cert_p (ksba_cert_t cert); int gpgsm_cert_use_ocsp_p (ksba_cert_t cert); int gpgsm_cert_has_well_known_private_key (ksba_cert_t cert); int gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b); int gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert, certlist_t *listaddr, int is_encrypt_to); int gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret, certlist_t *listaddr, int is_encrypt_to); void gpgsm_release_certlist (certlist_t list); int gpgsm_find_cert (ctrl_t ctrl, const char *name, ksba_sexp_t keyid, ksba_cert_t *r_cert, int allow_ambiguous); /*-- keylist.c --*/ gpg_error_t gpgsm_list_keys (ctrl_t ctrl, strlist_t names, estream_t fp, unsigned int mode); gpg_error_t gpgsm_show_certs (ctrl_t ctrl, int nfiles, char **files, estream_t fp); /*-- import.c --*/ int gpgsm_import (ctrl_t ctrl, int in_fd, int reimport_mode); int gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files, int (*of)(const char *fname)); /*-- export.c --*/ void gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream); void gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream, int rawmode); /*-- delete.c --*/ int gpgsm_delete (ctrl_t ctrl, strlist_t names); /*-- verify.c --*/ int gpgsm_verify (ctrl_t ctrl, int in_fd, int data_fd, estream_t out_fp); /*-- sign.c --*/ int gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert); int gpgsm_sign (ctrl_t ctrl, certlist_t signerlist, int data_fd, int detached, estream_t out_fp); /*-- encrypt.c --*/ int gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist, int in_fd, estream_t out_fp); /*-- decrypt.c --*/ gpg_error_t ecdh_derive_kek (unsigned char *key, unsigned int keylen, int hash_algo, const char *wrap_algo_str, const void *secret, unsigned int secretlen, const void *ukm, unsigned int ukmlen); int gpgsm_decrypt (ctrl_t ctrl, int in_fd, estream_t out_fp); /*-- certreqgen.c --*/ int gpgsm_genkey (ctrl_t ctrl, estream_t in_stream, estream_t out_stream); /*-- certreqgen-ui.c --*/ void gpgsm_gencertreq_tty (ctrl_t ctrl, estream_t out_stream); /*-- qualified.c --*/ gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, char *country); gpg_error_t gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert); gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert); /*-- call-agent.c --*/ int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, unsigned char **r_buf, size_t *r_buflen); int gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc, unsigned char *digest, size_t digestlen, int digestalgo, unsigned char **r_buf, size_t *r_buflen); int gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, ksba_const_sexp_t ciphertext, char **r_buf, size_t *r_buflen); int gpgsm_agent_genkey (ctrl_t ctrl, ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey); int gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip, ksba_sexp_t *r_pubkey); int gpgsm_agent_scd_serialno (ctrl_t ctrl, char **r_serialno); int gpgsm_agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list); int gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert, const char *hexfpr, struct rootca_flags_s *rootca_flags); int gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip); int gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert); int gpgsm_agent_learn (ctrl_t ctrl); int gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc); gpg_error_t gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc); gpg_error_t gpgsm_agent_send_nop (ctrl_t ctrl); gpg_error_t gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno); gpg_error_t gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg, int repeat, char **r_passphrase); gpg_error_t gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen); gpg_error_t gpgsm_agent_import_key (ctrl_t ctrl, const void *key, size_t keylen); gpg_error_t gpgsm_agent_export_key (ctrl_t ctrl, const char *keygrip, const char *desc, unsigned char **r_result, size_t *r_resultlen); /*-- call-dirmngr.c --*/ gpg_error_t gpgsm_dirmngr_isvalid (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t issuer_cert, - int use_ocsp); + int use_ocsp, + gnupg_isotime_t r_revoked_at, + char **r_reason); int gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, const char *uri, int cache_only, void (*cb)(void*, ksba_cert_t), void *cb_value); int gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command, int argc, char **argv); /*-- misc.c --*/ void gpgsm_print_further_info (const char *format, ...) GPGRT_ATTR_PRINTF(1,2); void setup_pinentry_env (void); gpg_error_t transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo, unsigned char **r_newsigval, size_t *r_newsigvallen); gcry_sexp_t gpgsm_ksba_cms_get_sig_val (ksba_cms_t cms, int idx); int gpgsm_get_hash_algo_from_sigval (gcry_sexp_t sigval, unsigned int *r_pkalgo_flags); #endif /*GPGSM_H*/ diff --git a/sm/keylist.c b/sm/keylist.c index fb2c3bad5..fabd82224 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -1,1879 +1,1897 @@ /* keylist.c - Print certificates in various formats. * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2008, 2009, * 2010, 2011 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 . */ #include #include #include #include #include #include #include #include "gpgsm.h" #include #include #include "keydb.h" #include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */ #include "../common/i18n.h" #include "../common/tlv.h" #include "../common/compliance.h" #include "../common/pkscreening.h" struct list_external_parm_s { ctrl_t ctrl; estream_t fp; int print_header; int with_colons; int with_chain; int raw_mode; }; /* Do not print this extension in the list of extensions. This is set for oids which are already available via ksba functions. */ #define OID_FLAG_SKIP 1 /* The extension is a simple UTF8String and should be printed. */ #define OID_FLAG_UTF8 2 /* The extension can be trnted as a hex string. */ #define OID_FLAG_HEX 4 /* Define if this specififies a key purpose. */ #define OID_FLAG_KP 8 /* A table mapping OIDs to a descriptive string. */ static struct { char *oid; char *name; unsigned int flag; /* A flag as described above. */ } oidtranstbl[] = { /* Algorithms. */ { "1.2.840.10040.4.1", "dsa" }, { "1.2.840.10040.4.3", "dsaWithSha1" }, { "1.2.840.113549.1.1.1", "rsaEncryption" }, { "1.2.840.113549.1.1.2", "md2WithRSAEncryption" }, { "1.2.840.113549.1.1.3", "md4WithRSAEncryption" }, { "1.2.840.113549.1.1.4", "md5WithRSAEncryption" }, { "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" }, { "1.2.840.113549.1.1.7", "rsaOAEP" }, { "1.2.840.113549.1.1.8", "rsaOAEP-MGF" }, { "1.2.840.113549.1.1.9", "rsaOAEP-pSpecified" }, { "1.2.840.113549.1.1.10", "rsaPSS" }, { "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" }, { "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" }, { "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" }, { "1.3.14.3.2.26", "sha1" }, { "1.3.14.3.2.29", "sha-1WithRSAEncryption" }, { "1.3.36.3.3.1.2", "rsaSignatureWithripemd160" }, /* Telesec extensions. */ { "0.2.262.1.10.12.0", "certExtensionLiabilityLimitationExt" }, { "0.2.262.1.10.12.1", "telesecCertIdExt" }, { "0.2.262.1.10.12.2", "telesecPolicyIdentifier" }, { "0.2.262.1.10.12.3", "telesecPolicyQualifierID" }, { "0.2.262.1.10.12.4", "telesecCRLFilteredExt" }, { "0.2.262.1.10.12.5", "telesecCRLFilterExt"}, { "0.2.262.1.10.12.6", "telesecNamingAuthorityExt" }, #define OIDSTR_restriction \ "1.3.36.8.3.8" { OIDSTR_restriction, "restriction", OID_FLAG_UTF8 }, /* PKIX private extensions. */ { "1.3.6.1.5.5.7.1.1", "authorityInfoAccess" }, { "1.3.6.1.5.5.7.1.2", "biometricInfo" }, { "1.3.6.1.5.5.7.1.3", "qcStatements" }, { "1.3.6.1.5.5.7.1.4", "acAuditIdentity" }, { "1.3.6.1.5.5.7.1.5", "acTargeting" }, { "1.3.6.1.5.5.7.1.6", "acAaControls" }, { "1.3.6.1.5.5.7.1.7", "sbgp-ipAddrBlock" }, { "1.3.6.1.5.5.7.1.8", "sbgp-autonomousSysNum" }, { "1.3.6.1.5.5.7.1.9", "sbgp-routerIdentifier" }, { "1.3.6.1.5.5.7.1.10", "acProxying" }, { "1.3.6.1.5.5.7.1.11", "subjectInfoAccess" }, { "1.3.6.1.5.5.7.3.1", "serverAuth", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.2", "clientAuth", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.3", "codeSigning", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.4", "emailProtection", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.5", "ipsecEndSystem", OID_FLAG_KP }, /* historic */ { "1.3.6.1.5.5.7.3.6", "ipsecTunnel", OID_FLAG_KP }, /* historic */ { "1.3.6.1.5.5.7.3.7", "ipsecUser", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.8", "timeStamping", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.9", "ocspSigning", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.10", "dvcs", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.11", "sbgpCertAAServerAuth", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.13", "eapOverPPP", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.14", "wlanSSID", OID_FLAG_KP }, { "1.3.6.1.5.5.7.3.17", "ipsecIKE", OID_FLAG_KP }, /* rfc-4945 */ { "1.3.6.1.5.5.7.48.1", "ocsp" }, { "1.3.6.1.5.5.7.48.1.5", "ocspNoCheck", OID_FLAG_KP }, { "1.3.6.1.5.5.7.48.2", "caIssuers" }, { "1.3.6.1.5.5.7.48.3", "timeStamping" }, { "1.3.6.1.5.5.7.48.5", "caRepository" }, /* X.509 id-ce */ { "2.5.29.14", "subjectKeyIdentifier", OID_FLAG_SKIP}, { "2.5.29.15", "keyUsage", OID_FLAG_SKIP}, { "2.5.29.16", "privateKeyUsagePeriod" }, { "2.5.29.17", "subjectAltName", OID_FLAG_SKIP}, { "2.5.29.18", "issuerAltName", OID_FLAG_SKIP}, { "2.5.29.19", "basicConstraints", OID_FLAG_SKIP}, { "2.5.29.20", "cRLNumber" }, { "2.5.29.21", "cRLReason" }, { "2.5.29.22", "expirationDate" }, { "2.5.29.23", "instructionCode" }, { "2.5.29.24", "invalidityDate" }, { "2.5.29.27", "deltaCRLIndicator" }, { "2.5.29.28", "issuingDistributionPoint" }, { "2.5.29.29", "certificateIssuer" }, { "2.5.29.30", "nameConstraints" }, { "2.5.29.31", "cRLDistributionPoints", OID_FLAG_SKIP}, { "2.5.29.32", "certificatePolicies", OID_FLAG_SKIP}, { "2.5.29.32.0", "anyPolicy" }, { "2.5.29.33", "policyMappings" }, { "2.5.29.35", "authorityKeyIdentifier", OID_FLAG_SKIP}, { "2.5.29.36", "policyConstraints" }, { "2.5.29.37", "extKeyUsage", OID_FLAG_SKIP}, { "2.5.29.37.0", "anyExtendedKeyUsage", OID_FLAG_KP}, { "2.5.29.46", "freshestCRL" }, { "2.5.29.54", "inhibitAnyPolicy" }, /* Netscape certificate extensions. */ { "2.16.840.1.113730.1.1", "netscape-cert-type" }, { "2.16.840.1.113730.1.2", "netscape-base-url" }, { "2.16.840.1.113730.1.3", "netscape-revocation-url" }, { "2.16.840.1.113730.1.4", "netscape-ca-revocation-url" }, { "2.16.840.1.113730.1.7", "netscape-cert-renewal-url" }, { "2.16.840.1.113730.1.8", "netscape-ca-policy-url" }, { "2.16.840.1.113730.1.9", "netscape-homePage-url" }, { "2.16.840.1.113730.1.10", "netscape-entitylogo" }, { "2.16.840.1.113730.1.11", "netscape-userPicture" }, { "2.16.840.1.113730.1.12", "netscape-ssl-server-name" }, { "2.16.840.1.113730.1.13", "netscape-comment" }, { "2.16.840.1.113730.4.1", "serverGatedCrypto.ns", OID_FLAG_KP }, /* GnuPG extensions */ { "1.3.6.1.4.1.11591.2.1.1", "pkaAddress" }, { "1.3.6.1.4.1.11591.2.2.1", "standaloneCertificate" }, { "1.3.6.1.4.1.11591.2.2.2", "wellKnownPrivateKey" }, { "1.3.6.1.4.1.11591.2.6.1", "gpgUsageCert", OID_FLAG_KP }, { "1.3.6.1.4.1.11591.2.6.2", "gpgUsageSign", OID_FLAG_KP }, { "1.3.6.1.4.1.11591.2.6.3", "gpgUsageEncr", OID_FLAG_KP }, { "1.3.6.1.4.1.11591.2.6.4", "gpgUsageAuth", OID_FLAG_KP }, /* Extensions used by the Bundesnetzagentur. */ { "1.3.6.1.4.1.8301.3.5", "validityModel" }, /* Yubikey extensions for attestation certificates. */ { "1.3.6.1.4.1.41482.3.3", "yubikey-firmware-version", OID_FLAG_HEX }, { "1.3.6.1.4.1.41482.3.7", "yubikey-serial-number", OID_FLAG_HEX }, { "1.3.6.1.4.1.41482.3.8", "yubikey-pin-touch-policy", OID_FLAG_HEX }, { "1.3.6.1.4.1.41482.3.9", "yubikey-formfactor", OID_FLAG_HEX }, /* Microsoft extensions. */ { "1.3.6.1.4.1.311.3.10.3.12","ms-old-documentSigning", OID_FLAG_KP }, { "1.3.6.1.4.1.311.10.3.3", "ms-serverGatedCrypto", OID_FLAG_KP }, { "1.3.6.1.4.1.311.10.3.11","ms-keyRecovery", OID_FLAG_KP }, { "1.3.6.1.4.1.311.10.3.12","ms-documentSigning", OID_FLAG_KP }, { "1.3.6.1.4.1.311.10.3.4", "ms-encryptedFileSystem", OID_FLAG_KP }, { "1.3.6.1.4.1.311.10.3.4.1","ms-efsRecovery", OID_FLAG_KP }, { "1.3.6.1.4.1.311.20.2.1", "ms-enrollmentAgent", OID_FLAG_KP }, { "1.3.6.1.4.1.311.20.2.2", "ms-smartcardLogon", OID_FLAG_KP }, { "1.3.6.1.4.1.311.21.5", "ms-caExchange", OID_FLAG_KP }, { "1.3.6.1.4.1.311.21.6", "ms-keyRecovery", OID_FLAG_KP }, { "1.3.6.1.4.1.311.21.19", "ms-dsEmailReplication", OID_FLAG_KP }, /* Other vendor extensions. */ { "1.3.6.1.4.1.30205.13.1.1", "trusted-disk", OID_FLAG_KP }, { "1.2.840.113583.1.1.5", "pdfAuthenticDocumentsTrust", OID_FLAG_KP }, { "1.3.6.1.4.1.6449.1.3.5.2", "comodoCertifiedDeliveryService", OID_FLAG_KP }, /* ARRL */ { "1.3.6.1.4.1.12348.1.1", "lotw-callsign" }, { "1.3.6.1.4.1.12348.1.2", "lotw-qso-first-date" }, { "1.3.6.1.4.1.12348.1.3", "lotw-qso-end-date" }, { "1.3.6.1.4.1.12348.1.4", "lotw-dxcc-entity" }, /* { "1.3.6.1.4.1.12348.1.5", "lotw-fixme" }, */ { NULL } }; /* Return the description for OID; if no description is available NULL * is returned. If MATCHFLAG is set the flag of the OID must match * MATCHFLAG; otherwise NULL is returned. */ static const char * get_oid_desc (const char *oid, unsigned int matchflag, unsigned int *flag) { int i; if (oid) for (i=0; oidtranstbl[i].oid; i++) if (!strcmp (oidtranstbl[i].oid, oid) && (!matchflag || (oidtranstbl[i].flag & matchflag))) { if (flag) *flag = oidtranstbl[i].flag; return oidtranstbl[i].name; } if (flag) *flag = 0; return NULL; } static void print_key_data (ksba_cert_t cert, estream_t fp) { #if 0 int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0; int i; for(i=0; i < n; i++ ) { es_fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) ); mpi_print(stdout, pk->pkey[i], 1 ); putchar(':'); putchar('\n'); } #else (void)cert; (void)fp; #endif } /* Various public key screenings. (Right now just ROCA). With * COLON_MODE set the output is formatted for use in the compliance * field of a colon listing. */ static void print_pk_screening (ksba_cert_t cert, int colon_mode, estream_t fp) { gpg_error_t err; gcry_mpi_t modulus; modulus = gpgsm_get_rsa_modulus (cert); if (modulus) { err = screen_key_for_roca (modulus); if (!err) ; else if (gpg_err_code (err) == GPG_ERR_TRUE) { if (colon_mode) es_fprintf (fp, colon_mode > 1? " %d":"%d", 6001); else es_fprintf (fp, " screening: ROCA vulnerability detected\n"); } else if (!colon_mode) es_fprintf (fp, " screening: [ROCA check failed: %s]\n", gpg_strerror (err)); gcry_mpi_release (modulus); } } static void print_capabilities (ksba_cert_t cert, int algo, estream_t fp) { gpg_error_t err; unsigned int use; unsigned int is_encr, is_sign, is_cert; size_t buflen; char buffer[1]; err = ksba_cert_get_user_data (cert, "is_qualified", &buffer, sizeof (buffer), &buflen); if (!err && buflen) { if (*buffer) es_putc ('q', fp); } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) ; /* Don't know - will not get marked as 'q' */ else log_debug ("get_user_data(is_qualified) failed: %s\n", gpg_strerror (err)); err = ksba_cert_get_key_usage (cert, &use); if (gpg_err_code (err) == GPG_ERR_NO_DATA) { es_putc ('e', fp); es_putc ('s', fp); es_putc ('c', fp); es_putc ('E', fp); es_putc ('S', fp); es_putc ('C', fp); return; } if (err) { log_error (_("error getting key usage information: %s\n"), gpg_strerror (err)); return; } is_encr = is_sign = is_cert = 0; if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) is_encr = 1; if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) is_sign = 1; if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) is_cert = 1; /* We need to returned the faked key usage to frontends so that they * can select the right key. Note that we don't do this for the * human readable keyUsage. */ if ((algo == GCRY_PK_ECC || (opt.compat_flags & COMPAT_ALLOW_KA_TO_ENCR)) && (use & KSBA_KEYUSAGE_KEY_AGREEMENT)) is_encr = 1; if (is_encr) es_putc ('e', fp); if (is_sign) es_putc ('s', fp); if (is_cert) es_putc ('c', fp); if (is_encr) es_putc ('E', fp); if (is_sign) es_putc ('S', fp); if (is_cert) es_putc ('C', fp); } static void print_time (gnupg_isotime_t t, estream_t fp) { if (!t || !*t) ; else es_fputs (t, fp); } /* Return an allocated string with the email address extracted from a DN. Note hat we use this code also in ../kbx/keybox-blob.c. */ static char * email_kludge (const char *name) { const char *p, *string; unsigned char *buf; int n; string = name; for (;;) { p = strstr (string, "1.2.840.113549.1.9.1=#"); if (!p) return NULL; if (p == name || (p > string+1 && p[-1] == ',' && p[-2] != '\\')) { name = p + 22; break; } string = p + 22; } /* This looks pretty much like an email address in the subject's DN we use this to add an additional user ID entry. This way, OpenSSL generated keys get a nicer and usable listing. */ for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++) ; if (!n) return NULL; buf = xtrymalloc (n+3); if (!buf) return NULL; /* oops, out of core */ *buf = '<'; for (n=1, p=name; hexdigitp (p); p +=2, n++) buf[n] = xtoi_2 (p); buf[n++] = '>'; buf[n] = 0; return (char*)buf; } /* Print the compliance flags to field 18. ALGO is the gcrypt algo * number. NBITS is the length of the key in bits. */ static void print_compliance_flags (ksba_cert_t cert, int algo, unsigned int nbits, estream_t fp) { int indent = 0; int hashalgo; /* Note that we do not need to test for PK_ALGO_FLAG_RSAPSS because * that is not a property of the key but one of the created * signature. */ if (gnupg_pk_is_compliant (CO_DE_VS, algo, 0, NULL, nbits, NULL)) { hashalgo = gcry_md_map_name (ksba_cert_get_digest_algo (cert)); if (gnupg_digest_is_compliant (CO_DE_VS, hashalgo)) { es_fputs (gnupg_status_compliance_flag (CO_DE_VS), fp); indent = 1; } } if (opt.with_key_screening) print_pk_screening (cert, 1+indent, fp); } /* List one certificate in colon mode */ static void list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity, estream_t fp, int have_secret) { int rc; int idx; char truststring[2]; char *p; ksba_sexp_t sexp; char *fpr; ksba_isotime_t t; gpg_error_t valerr; int algo; unsigned int nbits; char *curve = NULL; const char *chain_id; char *chain_id_buffer = NULL; int is_root = 0; char *kludge_uid; if (ctrl->with_validation) valerr = gpgsm_validate_chain (ctrl, cert, GNUPG_ISOTIME_NONE, NULL, 1, NULL, 0, NULL); else valerr = 0; /* We need to get the fingerprint and the chaining ID in advance. */ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); { ksba_cert_t next; rc = gpgsm_walk_cert_chain (ctrl, cert, &next); if (!rc) /* We known the issuer's certificate. */ { p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1); chain_id_buffer = p; chain_id = chain_id_buffer; ksba_cert_release (next); } else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { /* We have reached the root certificate. */ chain_id = fpr; is_root = 1; } else chain_id = NULL; } es_fputs (have_secret? "crs:":"crt:", fp); /* Note: We can't use multiple flags, like "ei", because the validation check does only return one error. */ truststring[0] = 0; truststring[1] = 0; if ((validity & VALIDITY_REVOKED) || gpg_err_code (valerr) == GPG_ERR_CERT_REVOKED) *truststring = 'r'; else if (gpg_err_code (valerr) == GPG_ERR_CERT_EXPIRED) *truststring = 'e'; else { /* Lets also check whether the certificate under question expired. This is merely a hack until we found a proper way to store the expiration flag in the keybox. */ ksba_isotime_t current_time, not_after; gnupg_get_isotime (current_time); if (!opt.ignore_expiration && !ksba_cert_get_validity (cert, 1, not_after) && *not_after && strcmp (current_time, not_after) > 0 ) *truststring = 'e'; else if (valerr) { if (gpgsm_cert_has_well_known_private_key (cert)) *truststring = 'w'; /* Well, this is dummy CA. */ else *truststring = 'i'; } else if (ctrl->with_validation && !is_root) *truststring = 'f'; } /* If we have no truststring yet (i.e. the certificate might be good) and this is a root certificate, we ask the agent whether this is a trusted root certificate. */ if (!*truststring && is_root) { struct rootca_flags_s dummy_flags; if (gpgsm_cert_has_well_known_private_key (cert)) *truststring = 'w'; /* Well, this is dummy CA. */ else { rc = gpgsm_agent_istrusted (ctrl, cert, NULL, &dummy_flags); if (!rc) *truststring = 'u'; /* Yes, we trust this one (ultimately). */ else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) *truststring = 'n'; /* No, we do not trust this one. */ /* (in case of an error we can't tell anything.) */ } } if (*truststring) es_fputs (truststring, fp); algo = gpgsm_get_key_algo_info2 (cert, &nbits, &curve); es_fprintf (fp, ":%u:%d:%s:", nbits, algo, fpr+24); ksba_cert_get_validity (cert, 0, t); print_time (t, fp); es_putc (':', fp); ksba_cert_get_validity (cert, 1, t); print_time ( t, fp); es_putc (':', fp); /* Field 8, serial number: */ if ((sexp = ksba_cert_get_serial (cert))) { int len; const unsigned char *s = sexp; if (*s == '(') { s++; for (len=0; *s && *s != ':' && digitp (s); s++) len = len*10 + atoi_1 (s); if (*s == ':') for (s++; len; len--, s++) es_fprintf (fp,"%02X", *s); } xfree (sexp); } es_putc (':', fp); /* Field 9, ownertrust - not used here */ es_putc (':', fp); /* field 10, old user ID - we use it here for the issuer DN */ if ((p = ksba_cert_get_issuer (cert,0))) { es_write_sanitized (fp, p, strlen (p), ":", NULL); xfree (p); } es_putc (':', fp); /* Field 11, signature class - not used */ es_putc (':', fp); /* Field 12, capabilities: */ print_capabilities (cert, algo, fp); es_putc (':', fp); /* Field 13, not used: */ es_putc (':', fp); /* Field 14, not used: */ es_putc (':', fp); if (have_secret || ctrl->with_secret) { char *cardsn; p = gpgsm_get_keygrip_hexstring (cert); if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) && (cardsn || ctrl->with_secret)) { /* Field 15: Token serial number or secret key indicator. */ if (cardsn) es_fputs (cardsn, fp); else if (ctrl->with_secret) es_putc ('+', fp); } xfree (cardsn); xfree (p); } es_putc (':', fp); /* End of field 15. */ es_putc (':', fp); /* End of field 16. */ if (curve) es_fputs (curve, fp); es_putc (':', fp); /* End of field 17. */ print_compliance_flags (cert, algo, nbits, fp); es_putc (':', fp); /* End of field 18. */ es_putc ('\n', fp); /* FPR record */ es_fprintf (fp, "fpr:::::::::%s:::", fpr); /* Print chaining ID (field 13)*/ if (chain_id) es_fputs (chain_id, fp); es_putc (':', fp); es_putc ('\n', fp); xfree (fpr); fpr = NULL; chain_id = NULL; xfree (chain_id_buffer); chain_id_buffer = NULL; /* SHA256 FPR record */ fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA256); es_fprintf (fp, "fp2:::::::::%s::::\n", fpr); xfree (fpr); fpr = NULL; /* Always print the keygrip. */ if ( (p = gpgsm_get_keygrip_hexstring (cert))) { es_fprintf (fp, "grp:::::::::%s:\n", p); xfree (p); } if (opt.with_key_data) print_key_data (cert, fp); kludge_uid = NULL; for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++) { /* In the case that the same email address is in the subject DN as well as in an alternate subject name we avoid printing it a second time. */ if (kludge_uid && !strcmp (kludge_uid, p)) continue; es_fprintf (fp, "uid:%s::::::::", truststring); es_write_sanitized (fp, p, strlen (p), ":", NULL); es_putc (':', fp); es_putc (':', fp); es_putc ('\n', fp); if (!idx) { /* It would be better to get the faked email address from the keydb. But as long as we don't have a way to pass the meta data back, we just check it the same way as the code used to create the keybox meta data does */ kludge_uid = email_kludge (p); if (kludge_uid) { es_fprintf (fp, "uid:%s::::::::", truststring); es_write_sanitized (fp, kludge_uid, strlen (kludge_uid), ":", NULL); es_putc (':', fp); es_putc (':', fp); es_putc ('\n', fp); } } xfree (p); } xfree (kludge_uid); xfree (curve); } static void print_name_raw (estream_t fp, const char *string) { if (!string) es_fputs ("[error]", fp); else es_write_sanitized (fp, string, strlen (string), NULL, NULL); } static void print_names_raw (estream_t fp, int indent, ksba_name_t name) { int idx; const char *s; int indent_all; if ((indent_all = (indent < 0))) indent = - indent; if (!name) { es_fputs ("none\n", fp); return; } for (idx=0; (s = ksba_name_enum (name, idx)); idx++) { char *p = ksba_name_get_uri (name, idx); es_fprintf (fp, "%*s", idx||indent_all?indent:0, ""); es_write_sanitized (fp, p?p:s, strlen (p?p:s), NULL, NULL); es_putc ('\n', fp); xfree (p); } } static void print_utf8_extn_raw (estream_t fp, int indent, const unsigned char *der, size_t derlen) { gpg_error_t err; int class, tag, constructed, ndef; size_t objlen, hdrlen; if (indent < 0) indent = - indent; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_UTF8_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { es_fprintf (fp, "%*s[%s]\n", indent, "", gpg_strerror (err)); return; } es_fprintf (fp, "%*s(%.*s)\n", indent, "", (int)objlen, der); } static void print_utf8_extn (estream_t fp, int indent, const unsigned char *der, size_t derlen) { gpg_error_t err; int class, tag, constructed, ndef; size_t objlen, hdrlen; int indent_all; if ((indent_all = (indent < 0))) indent = - indent; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_UTF8_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { es_fprintf (fp, "%*s[%s%s]\n", indent_all? indent:0, "", _("Error - "), gpg_strerror (err)); return; } es_fprintf (fp, "%*s\"", indent_all? indent:0, ""); /* Fixme: we should implement word wrapping */ es_write_sanitized (fp, der, objlen, "\"", NULL); es_fputs ("\"\n", fp); } /* Print the extension described by (DER,DERLEN) in hex. */ static void print_hex_extn (estream_t fp, int indent, const unsigned char *der, size_t derlen) { if (indent < 0) indent = - indent; es_fprintf (fp, "%*s(", indent, ""); for (; derlen; der++, derlen--) es_fprintf (fp, "%02X%s", *der, derlen > 1? " ":""); es_fprintf (fp, ")\n"); } /* List one certificate in raw mode useful to have a closer look at the certificate. This one does no beautification and only minimal output sanitation. It is mainly useful for debugging. */ static void list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd, ksba_cert_t cert, estream_t fp, int have_secret, int with_validation) { gpg_error_t err; size_t off, len; ksba_sexp_t sexp, keyid; char *dn; ksba_isotime_t t; int idx, i; int is_ca, chainlen; unsigned int kusage; char *string, *p, *pend; const char *oid, *s; ksba_name_t name, name2; unsigned int reason; const unsigned char *cert_der = NULL; char *algostr; int algoid; (void)have_secret; es_fprintf (fp, " ID: 0x%08lX\n", gpgsm_get_short_fingerprint (cert, NULL)); sexp = ksba_cert_get_serial (cert); es_fputs (" S/N: ", fp); gpgsm_print_serial (fp, sexp); es_putc ('\n', fp); es_fputs (" (dec): ", fp); gpgsm_print_serial_decimal (fp, sexp); es_putc ('\n', fp); ksba_free (sexp); dn = ksba_cert_get_issuer (cert, 0); es_fputs (" Issuer: ", fp); print_name_raw (fp, dn); ksba_free (dn); es_putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++) { es_fputs (" aka: ", fp); print_name_raw (fp, dn); ksba_free (dn); es_putc ('\n', fp); } dn = ksba_cert_get_subject (cert, 0); es_fputs (" Subject: ", fp); print_name_raw (fp, dn); ksba_free (dn); es_putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++) { es_fputs (" aka: ", fp); print_name_raw (fp, dn); ksba_free (dn); es_putc ('\n', fp); } dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA256); es_fprintf (fp, " sha2_fpr: %s\n", dn?dn:"error"); xfree (dn); dn = gpgsm_get_fingerprint_string (cert, 0); es_fprintf (fp, " sha1_fpr: %s\n", dn?dn:"error"); xfree (dn); dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5); es_fprintf (fp, " md5_fpr: %s\n", dn?dn:"error"); xfree (dn); algoid = 0; algostr = gpgsm_pubkey_algo_string (cert, &algoid); /* For RSA we support printing an OpenPGP v4 fingerprint under the * assumption that the not-before date would be used as the OpenPGP * key creation date. */ if (algoid == GCRY_PK_RSA) { ksba_sexp_t pk; size_t pklen; const unsigned char *m, *e; size_t mlen, elen; unsigned char fpr20[20]; time_t tmpt; unsigned long keytime; pk = ksba_cert_get_public_key (cert); if (pk) { ksba_cert_get_validity (cert, 0, t); tmpt = isotime2epoch (t); keytime = (tmpt == (time_t)(-1))? 0 : (u32)tmpt; pklen = gcry_sexp_canon_len (pk, 0, NULL, NULL); if (!pklen) log_error ("libksba did not return a proper S-Exp\n"); else if (!get_rsa_pk_from_canon_sexp (pk, pklen, &m, &mlen, &e, &elen) && !compute_openpgp_fpr_rsa (4, keytime, m, mlen, e, elen, fpr20, NULL)) { char *fpr = bin2hex (fpr20, 20, NULL); es_fprintf (fp, " pgp_fpr: %s\n", fpr); xfree (fpr); } ksba_free (pk); } } dn = gpgsm_get_certid (cert); es_fprintf (fp, " certid: %s\n", dn?dn:"error"); xfree (dn); dn = gpgsm_get_keygrip_hexstring (cert); es_fprintf (fp, " keygrip: %s\n", dn?dn:"error"); xfree (dn); ksba_cert_get_validity (cert, 0, t); es_fputs (" notBefore: ", fp); gpgsm_print_time (fp, t); es_putc ('\n', fp); es_fputs (" notAfter: ", fp); ksba_cert_get_validity (cert, 1, t); gpgsm_print_time (fp, t); es_putc ('\n', fp); oid = ksba_cert_get_digest_algo (cert); s = get_oid_desc (oid, 0, NULL); es_fprintf (fp, " hashAlgo: %s%s%s%s\n", oid, s?" (":"",s?s:"",s?")":""); es_fprintf (fp, " keyType: %s\n", algostr? algostr : "[error]"); /* subjectKeyIdentifier */ es_fputs (" subjKeyId: ", fp); err = ksba_cert_get_subj_key_id (cert, NULL, &keyid); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) { if (gpg_err_code (err) == GPG_ERR_NO_DATA) es_fputs ("[none]\n", fp); else { gpgsm_print_serial (fp, keyid); ksba_free (keyid); es_putc ('\n', fp); } } else es_fputs ("[?]\n", fp); /* authorityKeyIdentifier */ es_fputs (" authKeyId: ", fp); err = ksba_cert_get_auth_key_id (cert, &keyid, &name, &sexp); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) { if (gpg_err_code (err) == GPG_ERR_NO_DATA || !name) es_fputs ("[none]\n", fp); else { gpgsm_print_serial (fp, sexp); ksba_free (sexp); es_putc ('\n', fp); print_names_raw (fp, -15, name); ksba_name_release (name); } if (keyid) { es_fputs (" authKeyId.ki: ", fp); gpgsm_print_serial (fp, keyid); ksba_free (keyid); es_putc ('\n', fp); } } else es_fputs ("[?]\n", fp); es_fputs (" keyUsage:", fp); err = ksba_cert_get_key_usage (cert, &kusage); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { if (err) es_fprintf (fp, " [error: %s]", gpg_strerror (err)); else { if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) es_fputs (" digitalSignature", fp); if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION)) es_fputs (" nonRepudiation", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) es_fputs (" keyEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT)) es_fputs (" dataEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT)) es_fputs (" keyAgreement", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN)) es_fputs (" certSign", fp); if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN)) es_fputs (" crlSign", fp); if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY)) es_fputs (" encipherOnly", fp); if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY)) es_fputs (" decipherOnly", fp); } es_putc ('\n', fp); } else es_fputs (" [none]\n", fp); es_fputs (" extKeyUsage: ", fp); err = ksba_cert_get_ext_key_usages (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else { p = string; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; s = get_oid_desc (p, OID_FLAG_KP, NULL); es_fputs (s ? s : p, fp); p = pend; if (*p != 'C') es_fputs (" (suggested)", fp); if ((p = strchr (p, '\n'))) { p++; es_fputs ("\n ", fp); } } xfree (string); } es_putc ('\n', fp); } else es_fputs ("[none]\n", fp); es_fputs (" policies: ", fp); err = ksba_cert_get_cert_policies (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else { p = string; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; s = get_oid_desc (p, OID_FLAG_KP, NULL); es_fputs (s?s:p, fp); p = pend; if (*p == 'C') es_fputs (" (critical)", fp); if ((p = strchr (p, '\n'))) { p++; es_fputs ("\n ", fp); } } xfree (string); } es_putc ('\n', fp); } else es_fputs ("[none]\n", fp); es_fputs (" chainLength: ", fp); err = ksba_cert_is_ca (cert, &is_ca, &chainlen); if (err || is_ca) { if (gpg_err_code (err) == GPG_ERR_NO_VALUE ) es_fprintf (fp, "[none]"); else if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else if (chainlen == -1) es_fputs ("unlimited", fp); else es_fprintf (fp, "%d", chainlen); es_putc ('\n', fp); } else es_fputs ("not a CA\n", fp); /* CRL distribution point */ for (idx=0; !(err=ksba_cert_get_crl_dist_point (cert, idx, &name, &name2, &reason)) ;idx++) { es_fputs (" crlDP: ", fp); print_names_raw (fp, 15, name); if (reason) { es_fputs (" reason: ", fp); if ( (reason & KSBA_CRLREASON_UNSPECIFIED)) es_fputs (" unused", fp); if ( (reason & KSBA_CRLREASON_KEY_COMPROMISE)) es_fputs (" keyCompromise", fp); if ( (reason & KSBA_CRLREASON_CA_COMPROMISE)) es_fputs (" caCompromise", fp); if ( (reason & KSBA_CRLREASON_AFFILIATION_CHANGED)) es_fputs (" affiliationChanged", fp); if ( (reason & KSBA_CRLREASON_SUPERSEDED)) es_fputs (" superseded", fp); if ( (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION)) es_fputs (" cessationOfOperation", fp); if ( (reason & KSBA_CRLREASON_CERTIFICATE_HOLD)) es_fputs (" certificateHold", fp); es_putc ('\n', fp); } es_fputs (" issuer: ", fp); print_names_raw (fp, 23, name2); ksba_name_release (name); ksba_name_release (name2); } if (err && gpg_err_code (err) != GPG_ERR_EOF && gpg_err_code (err) != GPG_ERR_NO_VALUE) es_fputs (" crlDP: [error]\n", fp); else if (!idx) es_fputs (" crlDP: [none]\n", fp); /* authorityInfoAccess. */ for (idx=0; !(err=ksba_cert_get_authority_info_access (cert, idx, &string, &name)); idx++) { es_fputs (" authInfo: ", fp); s = get_oid_desc (string, 0, NULL); es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":""); print_names_raw (fp, -15, name); ksba_name_release (name); ksba_free (string); } if (err && gpg_err_code (err) != GPG_ERR_EOF && gpg_err_code (err) != GPG_ERR_NO_VALUE) es_fputs (" authInfo: [error]\n", fp); else if (!idx) es_fputs (" authInfo: [none]\n", fp); /* subjectInfoAccess. */ for (idx=0; !(err=ksba_cert_get_subject_info_access (cert, idx, &string, &name)); idx++) { es_fputs (" subjectInfo: ", fp); s = get_oid_desc (string, 0, NULL); es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":""); print_names_raw (fp, -15, name); ksba_name_release (name); ksba_free (string); } if (err && gpg_err_code (err) != GPG_ERR_EOF && gpg_err_code (err) != GPG_ERR_NO_VALUE) es_fputs (" subjInfo: [error]\n", fp); else if (!idx) es_fputs (" subjInfo: [none]\n", fp); for (idx=0; !(err=ksba_cert_get_extension (cert, idx, &oid, &i, &off, &len));idx++) { unsigned int flag; s = get_oid_desc (oid, 0, &flag); if ((flag & OID_FLAG_SKIP)) continue; es_fprintf (fp, " %s: %s%s%s%s", i? "critExtn":" extn", oid, s?" (":"", s?s:"", s?")":""); if ((flag & OID_FLAG_UTF8)) { if (!cert_der) cert_der = ksba_cert_get_image (cert, NULL); log_assert (cert_der); es_fprintf (fp, "\n"); print_utf8_extn_raw (fp, -15, cert_der+off, len); } else if ((flag & OID_FLAG_HEX)) { if (!cert_der) cert_der = ksba_cert_get_image (cert, NULL); log_assert (cert_der); es_fprintf (fp, "\n"); print_hex_extn (fp, -15, cert_der+off, len); } else es_fprintf (fp, " [%d octets]\n", (int)len); } if (with_validation) { err = gpgsm_validate_chain (ctrl, cert, GNUPG_ISOTIME_NONE, NULL, 1, fp, 0, NULL); + if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED + && !check_isotime (ctrl->revoked_at)) + { + es_fputs (" revoked: ", fp); + gpgsm_print_time (fp, ctrl->revoked_at); + if (ctrl->revocation_reason) + es_fprintf (fp, " (%s)", ctrl->revocation_reason); + es_putc ('\n', fp); + } if (!err) es_fprintf (fp, " [certificate is good]\n"); else es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err)); } if (hd) { unsigned int blobflags; err = keydb_get_flags (hd, KEYBOX_FLAG_BLOB, 0, &blobflags); if (err) es_fprintf (fp, " [error getting keyflags: %s]\n",gpg_strerror (err)); else if ((blobflags & KEYBOX_FLAG_BLOB_EPHEMERAL)) es_fprintf (fp, " [stored as ephemeral]\n"); } xfree (algostr); } /* List one certificate in standard mode */ static void list_cert_std (ctrl_t ctrl, ksba_cert_t cert, estream_t fp, int have_secret, int with_validation) { gpg_error_t err; ksba_sexp_t sexp; char *dn; ksba_isotime_t t; int idx; int is_ca, chainlen; unsigned int kusage; char *string, *p, *pend; size_t off, len; const char *oid, *s; const unsigned char *cert_der = NULL; es_fprintf (fp, " ID: 0x%08lX\n", gpgsm_get_short_fingerprint (cert, NULL)); sexp = ksba_cert_get_serial (cert); es_fputs (" S/N: ", fp); gpgsm_print_serial (fp, sexp); es_putc ('\n', fp); es_fputs (" (dec): ", fp); gpgsm_print_serial_decimal (fp, sexp); es_putc ('\n', fp); ksba_free (sexp); dn = ksba_cert_get_issuer (cert, 0); es_fputs (" Issuer: ", fp); gpgsm_es_print_name (fp, dn); ksba_free (dn); es_putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++) { es_fputs (" aka: ", fp); gpgsm_es_print_name (fp, dn); ksba_free (dn); es_putc ('\n', fp); } dn = ksba_cert_get_subject (cert, 0); es_fputs (" Subject: ", fp); gpgsm_es_print_name (fp, dn); ksba_free (dn); es_putc ('\n', fp); for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++) { es_fputs (" aka: ", fp); gpgsm_es_print_name (fp, dn); ksba_free (dn); es_putc ('\n', fp); } ksba_cert_get_validity (cert, 0, t); es_fputs (" validity: ", fp); gpgsm_print_time (fp, t); es_fputs (" through ", fp); ksba_cert_get_validity (cert, 1, t); gpgsm_print_time (fp, t); es_putc ('\n', fp); { char *algostr; algostr = gpgsm_pubkey_algo_string (cert, NULL); es_fprintf (fp, " key type: %s\n", algostr? algostr : "[error]"); xfree (algostr); } err = ksba_cert_get_key_usage (cert, &kusage); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { es_fputs (" key usage:", fp); if (err) es_fprintf (fp, " [error: %s]", gpg_strerror (err)); else { if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) es_fputs (" digitalSignature", fp); if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION)) es_fputs (" nonRepudiation", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) es_fputs (" keyEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT)) es_fputs (" dataEncipherment", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT)) es_fputs (" keyAgreement", fp); if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN)) es_fputs (" certSign", fp); if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN)) es_fputs (" crlSign", fp); if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY)) es_fputs (" encipherOnly", fp); if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY)) es_fputs (" decipherOnly", fp); } es_putc ('\n', fp); } err = ksba_cert_get_ext_key_usages (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { es_fputs ("ext key usage: ", fp); if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else { p = string; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; s = get_oid_desc (p, OID_FLAG_KP, NULL); es_fputs (s? s : p, fp); p = pend; if (*p != 'C') es_fputs (" (suggested)", fp); if ((p = strchr (p, '\n'))) { p++; es_fputs (", ", fp); } } xfree (string); } es_putc ('\n', fp); } /* Print restrictions. */ for (idx=0; !(err=ksba_cert_get_extension (cert, idx, &oid, NULL, &off, &len));idx++) { if (!strcmp (oid, OIDSTR_restriction) ) { if (!cert_der) cert_der = ksba_cert_get_image (cert, NULL); log_assert (cert_der); es_fputs (" restriction: ", fp); print_utf8_extn (fp, 15, cert_der+off, len); } } /* Print policies. */ err = ksba_cert_get_cert_policies (cert, &string); if (gpg_err_code (err) != GPG_ERR_NO_DATA) { es_fputs (" policies: ", fp); if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else { for (p=string; *p; p++) { if (*p == '\n') *p = ','; } es_write_sanitized (fp, string, strlen (string), NULL, NULL); xfree (string); } es_putc ('\n', fp); } err = ksba_cert_is_ca (cert, &is_ca, &chainlen); if (err || is_ca) { es_fputs (" chain length: ", fp); if (gpg_err_code (err) == GPG_ERR_NO_VALUE ) es_fprintf (fp, "none"); else if (err) es_fprintf (fp, "[error: %s]", gpg_strerror (err)); else if (chainlen == -1) es_fputs ("unlimited", fp); else es_fprintf (fp, "%d", chainlen); es_putc ('\n', fp); } if (opt.with_md5_fingerprint) { dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5); es_fprintf (fp, " md5 fpr: %s\n", dn?dn:"error"); xfree (dn); } dn = gpgsm_get_fingerprint_string (cert, 0); es_fprintf (fp, " sha1 fpr: %s\n", dn?dn:"error"); xfree (dn); dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA256); es_fprintf (fp, " sha2 fpr: %s\n", dn?dn:"error"); xfree (dn); if (opt.with_keygrip) { dn = gpgsm_get_keygrip_hexstring (cert); if (dn) { es_fprintf (fp, " keygrip: %s\n", dn); xfree (dn); } } if (opt.with_key_screening) print_pk_screening (cert, 0, fp); if (have_secret) { char *cardsn; p = gpgsm_get_keygrip_hexstring (cert); if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) && cardsn) es_fprintf (fp, " card s/n: %s\n", cardsn); xfree (cardsn); xfree (p); } if (with_validation) { gpg_error_t tmperr; size_t buflen; char buffer[1]; err = gpgsm_validate_chain (ctrl, cert, GNUPG_ISOTIME_NONE, NULL, 1, fp, 0, NULL); + if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED + && !check_isotime (ctrl->revoked_at)) + { + es_fputs (" revoked: ", fp); + gpgsm_print_time (fp, ctrl->revoked_at); + if (ctrl->revocation_reason) + es_fprintf (fp, " (%s)", ctrl->revocation_reason); + es_putc ('\n', fp); + } tmperr = ksba_cert_get_user_data (cert, "is_qualified", &buffer, sizeof (buffer), &buflen); if (!tmperr && buflen) { if (*buffer) es_fputs (" [qualified]\n", fp); } else if (gpg_err_code (tmperr) == GPG_ERR_NOT_FOUND) ; /* Don't know - will not get marked as 'q' */ else log_debug ("get_user_data(is_qualified) failed: %s\n", gpg_strerror (tmperr)); if (!err) es_fprintf (fp, " [certificate is good]\n"); else es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err)); } if (opt.debug) es_fflush (fp); } /* Same as standard mode list all certifying certs too. */ static void list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd, ksba_cert_t cert, int raw_mode, estream_t fp, int with_validation) { ksba_cert_t next = NULL; int depth = 0; if (raw_mode) list_cert_raw (ctrl, hd, cert, fp, 0, with_validation); else list_cert_std (ctrl, cert, fp, 0, with_validation); ksba_cert_ref (cert); while (!gpgsm_walk_cert_chain (ctrl, cert, &next)) { es_fputs ("Certified by\n", fp); if (++depth > 50) { es_fputs (_("certificate chain too long\n"), fp); break; } ksba_cert_release (cert); if (raw_mode) list_cert_raw (ctrl, hd, next, fp, 0, with_validation); else list_cert_std (ctrl, next, fp, 0, with_validation); cert = next; } ksba_cert_release (cert); es_putc ('\n', fp); } /* List all internal keys or just the keys given as NAMES. MODE is a bit vector to specify what keys are to be included; see gpgsm_list_keys (below) for details. If RAW_MODE is true, the raw output mode will be used instead of the standard beautified one. */ static gpg_error_t list_internal_keys (ctrl_t ctrl, strlist_t names, estream_t fp, unsigned int mode, int raw_mode) { KEYDB_HANDLE hd; KEYDB_SEARCH_DESC *desc = NULL; strlist_t sl; int ndesc; ksba_cert_t cert = NULL; ksba_cert_t lastcert = NULL; gpg_error_t rc = 0; const char *lastresname, *resname; int have_secret; int want_ephemeral = ctrl->with_ephemeral_keys; hd = keydb_new (ctrl); if (!hd) { log_error ("keydb_new failed\n"); rc = gpg_error (GPG_ERR_GENERAL); goto leave; } if (!names) ndesc = 1; else { for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) ; } desc = xtrycalloc (ndesc, sizeof *desc); if (!ndesc) { rc = gpg_error_from_syserror (); log_error ("out of core\n"); goto leave; } if (!names) desc[0].mode = KEYDB_SEARCH_MODE_FIRST; else { for (ndesc=0, sl=names; sl; sl = sl->next) { rc = classify_user_id (sl->d, desc+ndesc, 0); if (rc) { log_error ("key '%s' not found: %s\n", sl->d, gpg_strerror (rc)); rc = 0; } else ndesc++; } } /* If all specifications are done by fingerprint or keygrip, we switch to ephemeral mode so that _all_ currently available and matching certificates are listed. */ if (!want_ephemeral && names && ndesc) { int i; for (i=0; (i < ndesc && (desc[i].mode == KEYDB_SEARCH_MODE_FPR || desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++) ; if (i == ndesc) want_ephemeral = 1; } if (want_ephemeral) keydb_set_ephemeral (hd, 1); /* 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 and to set this 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 so */ /* Suppress duplicates at least when they follow each other. */ lastresname = NULL; while (!(rc = keydb_search (ctrl, hd, desc, ndesc))) { unsigned int validity; if (!names) desc[0].mode = KEYDB_SEARCH_MODE_NEXT; rc = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &validity); if (rc) { log_error ("keydb_get_flags failed: %s\n", gpg_strerror (rc)); goto leave; } rc = keydb_get_cert (hd, &cert); if (rc) { log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); goto leave; } /* Skip duplicated certificates, at least if they follow each others. This works best if a single key is searched for and expected. FIXME: Non-sequential duplicates remain. */ if (gpgsm_certs_identical_p (cert, lastcert)) { ksba_cert_release (cert); cert = NULL; continue; } resname = keydb_get_resource_name (hd); if (lastresname != resname ) { int i; if (ctrl->no_server) { es_fprintf (fp, "%s\n", resname ); for (i=strlen(resname); i; i-- ) es_putc ('-', fp); es_putc ('\n', fp); lastresname = resname; } } have_secret = 0; if (mode) { char *p = gpgsm_get_keygrip_hexstring (cert); if (p) { rc = gpgsm_agent_havekey (ctrl, p); if (!rc) have_secret = 1; else if ( gpg_err_code (rc) != GPG_ERR_NO_SECKEY) goto leave; rc = 0; xfree (p); } } if (!mode || ((mode & 1) && !have_secret) || ((mode & 2) && have_secret) ) { if (ctrl->with_colons) list_cert_colon (ctrl, cert, validity, fp, have_secret); else if (ctrl->with_chain) list_cert_chain (ctrl, hd, cert, raw_mode, fp, ctrl->with_validation); else { if (raw_mode) list_cert_raw (ctrl, hd, cert, fp, have_secret, ctrl->with_validation); else list_cert_std (ctrl, cert, fp, have_secret, ctrl->with_validation); es_putc ('\n', fp); } } ksba_cert_release (lastcert); lastcert = cert; cert = NULL; } if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND) rc = 0; if (rc) log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); leave: ksba_cert_release (cert); ksba_cert_release (lastcert); xfree (desc); keydb_release (hd); return rc; } static void list_external_cb (void *cb_value, ksba_cert_t cert) { struct list_external_parm_s *parm = cb_value; if (keydb_store_cert (parm->ctrl, cert, 1, NULL)) log_error ("error storing certificate as ephemeral\n"); if (parm->print_header) { const char *resname = "[external keys]"; int i; es_fprintf (parm->fp, "%s\n", resname ); for (i=strlen(resname); i; i-- ) es_putc('-', parm->fp); es_putc ('\n', parm->fp); parm->print_header = 0; } if (parm->with_colons) list_cert_colon (parm->ctrl, cert, 0, parm->fp, 0); else if (parm->with_chain) list_cert_chain (parm->ctrl, NULL, cert, parm->raw_mode, parm->fp, 0); else { if (parm->raw_mode) list_cert_raw (parm->ctrl, NULL, cert, parm->fp, 0, 0); else list_cert_std (parm->ctrl, cert, parm->fp, 0, 0); es_putc ('\n', parm->fp); } } /* List external keys similar to internal one. Note: mode does not make sense here because it would be unwise to list external secret keys */ static gpg_error_t list_external_keys (ctrl_t ctrl, strlist_t names, estream_t fp, int raw_mode) { int rc; struct list_external_parm_s parm; parm.fp = fp; parm.ctrl = ctrl, parm.print_header = ctrl->no_server; parm.with_colons = ctrl->with_colons; parm.with_chain = ctrl->with_chain; parm.raw_mode = raw_mode; rc = gpgsm_dirmngr_lookup (ctrl, names, NULL, 0, list_external_cb, &parm); if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1 || gpg_err_code (rc) == GPG_ERR_NOT_FOUND) rc = 0; /* "Not found" is not an error here. */ if (rc) log_error ("listing external keys failed: %s\n", gpg_strerror (rc)); return rc; } /* List all keys or just the key given as NAMES. MODE controls the operation mode: Bit 0-2: 0 = list all public keys but don't flag secret ones 1 = list only public keys 2 = list only secret keys 3 = list secret and public keys Bit 6: list internal keys Bit 7: list external keys Bit 8: Do a raw format dump. */ gpg_error_t gpgsm_list_keys (ctrl_t ctrl, strlist_t names, estream_t fp, unsigned int mode) { gpg_error_t err = 0; if ((mode & (1<<6))) err = list_internal_keys (ctrl, names, fp, (mode & 3), (mode&256)); if (!err && (mode & (1<<7))) err = list_external_keys (ctrl, names, fp, (mode&256)); return err; } static gpg_error_t do_show_certs (ctrl_t ctrl, const char *fname, estream_t outfp) { gpg_error_t err; gnupg_ksba_io_t b64reader = NULL; ksba_reader_t reader; ksba_cert_t cert = NULL; estream_t fp; int any = 0; if (!fname || (fname[0] == '-' && !fname[1])) { fp = es_stdin; fname = "[stdin]"; } else { 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; } } err = 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 (err) { log_error ("can't create reader: %s\n", gpg_strerror (err)); goto leave; } /* We need to loop here to handle multiple PEM objects per file. */ do { ksba_cert_release (cert); cert = NULL; err = ksba_cert_new (&cert); if (err) goto leave; err = ksba_cert_read_der (cert, reader); if (err) goto leave; es_fprintf (outfp, "File ........: %s\n", fname); list_cert_raw (ctrl, NULL, cert, outfp, 0, 0); es_putc ('\n', outfp); any = 1; ksba_reader_clear (reader, NULL, NULL); } while (!gnupg_ksba_reader_eof_seen (b64reader)); leave: if (any && gpg_err_code (err) == GPG_ERR_EOF) err = 0; ksba_cert_release (cert); gnupg_ksba_destroy_reader (b64reader); if (fp != es_stdin) es_fclose (fp); return err; } /* Show a raw dump of the certificates found in the files given in * the arrag FILES. Write output to FP. */ gpg_error_t gpgsm_show_certs (ctrl_t ctrl, int nfiles, char **files, estream_t fp) { gpg_error_t saveerr = 0; gpg_error_t err; if (!nfiles) saveerr = do_show_certs (ctrl, NULL, fp); else { for (; nfiles; nfiles--, files++) { err = do_show_certs (ctrl, *files, fp); if (err && !saveerr) saveerr = err; } } return saveerr; }