diff --git a/sm/ChangeLog b/sm/ChangeLog index aa9ac91bc..5c6c5eb42 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,8 +1,41 @@ +2005-11-13 Werner Koch + + * call-agent.c (gpgsm_agent_get_confirmation): New. + + * keylist.c (list_cert_std): Print qualified status. + * qualified.c: New. + * certchain.c (gpgsm_validate_chain): Check for qualified + certificates. + + * certchain.c (gpgsm_basic_cert_check): Release keydb handle when + no-chain-validation is used. + +2005-11-11 Werner Koch + + * keylist.c (print_capabilities): Print is_qualified status. + +2005-10-28 Werner Koch + + * certdump.c (pretty_print_sexp): New. + (gpgsm_print_name2): Use it here. This allows proper printing of + DNS names as used with server certificates. + +2005-10-10 Werner Koch + + * keylist.c: Add pkaAdress OID as reference. + 2005-10-08 Marcus Brinkmann * Makefile.am (gpgsm_LDADD): Add ../gl/libgnu.a after ../common/libcommon.a. +2005-09-13 Werner Koch + + * verify.c (gpgsm_verify): Print a note if the unknown algorithm + is MD2. + * sign.c (gpgsm_sign): Ditto. + * certcheck.c (gpgsm_check_cert_sig): Ditto. + 2005-09-08 Werner Koch * export.c (popen_protect_tool): Add option --have-cert. We diff --git a/sm/Makefile.am b/sm/Makefile.am index 69a286e55..aba2081f8 100644 --- a/sm/Makefile.am +++ b/sm/Makefile.am @@ -49,7 +49,8 @@ gpgsm_SOURCES = \ import.c \ export.c \ delete.c \ - certreqgen.c + certreqgen.c \ + qualified.c gpgsm_LDADD = ../jnlib/libjnlib.a ../kbx/libkeybox.a \ diff --git a/sm/call-agent.c b/sm/call-agent.c index c47f6b19a..03a3a2e3f 100644 --- a/sm/call-agent.c +++ b/sm/call-agent.c @@ -693,3 +693,24 @@ gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc) return map_assuan_err (rc); } + + +/* Ask the agent to pop up a confirmation dialog with the text DESC + and an okay and cancel button. */ +gpg_error_t +gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc) +{ + int rc; + char *fpr; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (ctrl); + if (rc) + return rc; + + snprintf (line, DIM(line)-1, "GET_CONFIRMATION %s", desc); + line[DIM(line)-1] = 0; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return map_assuan_err (rc); +} diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c index ead117dfd..dd38a889b 100644 --- a/sm/call-dirmngr.c +++ b/sm/call-dirmngr.c @@ -276,7 +276,7 @@ inq_certificate (void *opaque, const char *line) { size_t n; - /* Send a certificate where a sourceKeyidentifier is included. */ + /* Send a certificate where a sourceKeyIdentifier is included. */ line += 12; while (*line == ' ') line++; diff --git a/sm/certchain.c b/sm/certchain.c index 02e5b409f..0c14fc88f 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -643,6 +643,9 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, 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. */ int lm = listmode; gnupg_get_isotime (current_time); @@ -771,6 +774,53 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, 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) + { + 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. */ + err = gpgsm_is_in_qualified_list (ctrl, subject_cert); + 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 toomuch 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)); + } + } + } + + + /* Check whether we really trust this root certificate. */ rc = gpgsm_agent_istrusted (ctrl, subject_cert); if (!rc) ; @@ -968,7 +1018,7 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, keydb_search_reset (kh); subject_cert = issuer_cert; issuer_cert = NULL; - } + } /* End chain traversal. */ if (!listmode) { @@ -996,6 +1046,27 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, } leave: + if (is_qualified != -1) + { + /* We figured something about the qualified signature capability + of the certificate under question. Store the result as user + data in the certificate object. We do this even if the + validation itself failed. */ + /* Fixme: We should set this flag for all certificates in the + chain for optimizing reasons. */ + char buf[1]; + gpg_error_t err; + + buf[0] = !!is_qualified; + err = ksba_cert_set_user_data (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 (r_exptime) gnupg_copy_time (r_exptime, exptime); xfree (issuer); @@ -1017,7 +1088,7 @@ gpgsm_basic_cert_check (ksba_cert_t cert) int rc = 0; char *issuer = NULL; char *subject = NULL; - KEYDB_HANDLE kh = keydb_new (0); + KEYDB_HANDLE kh; ksba_cert_t issuer_cert = NULL; if (opt.no_chain_validation) @@ -1026,6 +1097,7 @@ gpgsm_basic_cert_check (ksba_cert_t cert) return 0; } + kh = keydb_new (0); if (!kh) { log_error (_("failed to allocated keyDB handle\n")); diff --git a/sm/certcheck.c b/sm/certcheck.c index 84dfdb9ab..5fb376712 100644 --- a/sm/certcheck.c +++ b/sm/certcheck.c @@ -168,6 +168,10 @@ gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert) if (!algo) { log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); + if (algoid + && ( !strcmp (algoid, "1.2.840.113549.1.1.2") + ||!strcmp (algoid, "1.2.840.113549.2.2"))) + log_info (_("(this is the MD2 algorithm)\n")); return gpg_error (GPG_ERR_GENERAL); } rc = gcry_md_open (&md, algo, 0); diff --git a/sm/certdump.c b/sm/certdump.c index aae60e020..1f2ea7b18 100644 --- a/sm/certdump.c +++ b/sm/certdump.c @@ -484,6 +484,51 @@ print_dn_parts (FILE *fp, struct dn_array_s *dn, int translate) } +/* Print the S-Expression in BUF, which has a valid length of BUFLEN, + as a human readable string in one line to FP. */ +static void +pretty_print_sexp (FILE *fp, const unsigned char *buf, size_t buflen) +{ + size_t len; + gcry_sexp_t sexp; + char *result, *p; + + if ( gcry_sexp_sscan (&sexp, NULL, (const char*)buf, buflen) ) + { + fputs (_("[Error - invalid encoding]"), fp); + return; + } + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); + assert (len); + result = xtrymalloc (len); + if (!result) + { + fputs (_("[Error - out of core]"), fp); + gcry_sexp_release (sexp); + return; + } + len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len); + assert (len); + for (p = result; len; len--, p++) + { + if (*p == '\n') + { + if (len > 1) /* Avoid printing the trailing LF. */ + fputs ("\\n", fp); + } + else if (*p == '\r') + fputs ("\\r", fp); + else if (*p == '\v') + fputs ("\\v", fp); + else if (*p == '\t') + fputs ("\\t", fp); + else + putc (*p, fp); + } + xfree (result); + gcry_sexp_release (sexp); +} + void gpgsm_print_name2 (FILE *fp, const char *name, int translate) @@ -507,7 +552,9 @@ gpgsm_print_name2 (FILE *fp, const char *name, int translate) } } else if (*s == '(') - fputs (_("[Error - unknown encoding]"), fp); + { + pretty_print_sexp (fp, s, gcry_sexp_canon_len (s, 0, NULL, NULL)); + } else if (!((*s >= '0' && *s < '9') || (*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z'))) @@ -576,7 +623,7 @@ format_name_writer (void *cookie, const char *buffer, size_t size) /* Format NAME which is expected to be in rfc2253 format into a better human readable format. Caller must free the returned string. NULL is returned in case of an error. With TRANSLATE set to true the - name will be translated to the native encodig. Note that NAME is + name will be translated to the native encoding. Note that NAME is internally always UTF-8 encoded. */ char * gpgsm_format_name2 (const char *name, int translate) @@ -658,7 +705,7 @@ gpgsm_format_keydesc (ksba_cert_t cert) #ifdef ENABLE_NLS - /* The Assuan agent protol requires us to transmit utf-8 strings */ + /* The Assuan agent protocol requires us to transmit utf-8 strings */ orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL); #ifdef HAVE_LANGINFO_CODESET if (!orig_codeset) diff --git a/sm/gpgsm.h b/sm/gpgsm.h index 63d07a8c2..52eff359a 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -288,6 +288,10 @@ int gpgsm_decrypt (ctrl_t ctrl, int in_fd, FILE *out_fp); /*-- certreqgen.c --*/ int gpgsm_genkey (ctrl_t ctrl, int in_fd, FILE *out_fp); +/*-- qualified.c --*/ +gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert); +gpg_error_t gpgsm_qualified_consent (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, @@ -306,6 +310,7 @@ 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); /*-- call-dirmngr.c --*/ int gpgsm_dirmngr_isvalid (ctrl_t ctrl, diff --git a/sm/keylist.c b/sm/keylist.c index 8a4eb3cdb..51a066dab 100644 --- a/sm/keylist.c +++ b/sm/keylist.c @@ -66,6 +66,9 @@ struct { { "1.3.6.1.5.5.7.3.11", "sbgpCertAAServerAuth" }, { "1.3.6.1.5.5.7.3.13", "eapOverPPP" }, { "1.3.6.1.5.5.7.3.14", "wlanSSID" }, + + { "2.16.840.1.113730.4.1", "serverGatedCrypto.ns" }, /* Netscape. */ + { "1.3.6.1.4.1.311.10.3.3", "serverGatedCrypto.ms"}, /* Microsoft. */ { NULL, NULL } }; @@ -160,6 +163,9 @@ static struct { { "2.16.840.1.113730.1.12", "netscape-ssl-server-name" }, { "2.16.840.1.113730.1.13", "netscape-comment" }, + /* GnuPG extensions */ + { "1.3.6.1.4.1.11591.2.1.1", "pkaAddress" }, + { NULL } }; @@ -207,6 +213,21 @@ print_capabilities (ksba_cert_t cert, FILE *fp) { gpg_error_t err; unsigned int use; + size_t buflen; + char buffer[1]; + + err = ksba_cert_get_user_data (cert, "is_qualified", + &buffer, sizeof (buffer), &buflen); + if (!err && buflen) + { + if (*buffer) + 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) @@ -1032,9 +1053,28 @@ list_cert_std (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int have_secret, fprintf (fp, " fingerprint: %s\n", dn?dn:"error"); xfree (dn); + + if (with_validation) { + gpg_error_t tmperr; + size_t buflen; + char buffer[1]; + err = gpgsm_validate_chain (ctrl, cert, NULL, 1, fp, 0); + tmperr = ksba_cert_get_user_data (cert, "is_qualified", + &buffer, sizeof (buffer), &buflen); + if (!tmperr && buflen) + { + if (*buffer) + 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) fprintf (fp, " [certificate is good]\n"); else diff --git a/sm/qualified.c b/sm/qualified.c new file mode 100644 index 000000000..c4bd00f96 --- /dev/null +++ b/sm/qualified.c @@ -0,0 +1,264 @@ +/* qualified.c - Routines related to qualified signatures + * Copyright (C) 2005 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LOCALE_H +#include +#endif +#ifdef HAVE_LANGINFO_CODESET +#include +#endif + +#include "gpgsm.h" +#include "i18n.h" +#include + + +/* We open the file only once and keep the open file pointer as well + as the name of the file here. Note that, a listname not equal to + NULL indicates that this module has been intialized and if the + LISTFP is also NULL, no list of qualified signatures exists. */ +static char *listname; +static FILE *listfp; + + +/* Read the trustlist and return entry by entry. KEY must point to a + buffer of at least 41 characters. COUNTRY shall be a buffer of at + least 3 characters to receive the country code of that qualified + signature (i.e. "de" for German and "be" for Belgium). + + Reading a valid entry returns 0, EOF is indicated by GPG_ERR_EOF + and any other error condition is indicated by the appropriate error + code. */ +static gpg_error_t +read_list (char *key, char *country, int *lnr) +{ + gpg_error_t err; + int c, i, j; + char *p, line[256]; + + *key = 0; + *country = 0; + + if (!listname) + { + listname = make_filename (GNUPG_DATADIR, "qualified.txt", NULL); + listfp = fopen (listname, "r"); + if (!listfp && errno != ENOENT) + { + err = gpg_error_from_errno (errno); + log_error (_("can't open `%s': %s\n"), listname, gpg_strerror (err)); + return err; + } + } + + if (!listfp) + return gpg_error (GPG_ERR_EOF); + + do + { + if (!fgets (line, DIM(line)-1, listfp) ) + { + if (feof (listfp)) + return gpg_error (GPG_ERR_EOF); + return gpg_error_from_errno (errno); + } + + if (!*line || line[strlen(line)-1] != '\n') + { + /* Eat until end of line. */ + while ( (c=getc (listfp)) != EOF && c != '\n') + ; + return gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + } + ++*lnr; + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + } + while (!*p || *p == '\n' || *p == '#'); + + for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++) + if ( p[i] != ':' ) + key[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i]; + key[j] = 0; + if (j != 40 || !(spacep (p+i) || p[i] == '\n')) + { + log_error (_("invalid formatted fingerprint in `%s', line %d\n"), + listname, *lnr); + return gpg_error (GPG_ERR_BAD_DATA); + } + assert (p[i]); + i++; + while (spacep (p+i)) + i++; + if ( p[i] >= 'a' && p[i] <= 'z' + && p[i+1] >= 'a' && p[i+1] <= 'z' + && (spacep (p+i+2) || p[i+2] == '\n')) + { + country[0] = p[i]; + country[1] = p[i+1]; + country[2] = 0; + } + else + { + log_error (_("invalid country code in `%s', line %d\n"), listname, *lnr); + return gpg_error (GPG_ERR_BAD_DATA); + } + + return 0; +} + + + + +/* Check whether the certificate CERT is included in the list of + qualified certificates. This list is similar to the "trustlist.txt" + as maintained by gpg-agent and includes fingerprints of root + certificates to be used for qualified (legally binding like + handwritten) signatures. We keep this list system wide and not + per user because it is not a decision of the user. + + Returns: 0 if the certificate is included. GPG_ERR_NOT_FOUND if it + is not in the liost or any other error (e.g. if no list of + qualified signatures is available. */ +gpg_error_t +gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert) +{ + gpg_error_t err; + char *fpr; + char key[41]; + char country[2]; + int lnr = 0; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + if (!fpr) + return gpg_error (GPG_ERR_GENERAL); + + if (listfp) + rewind (listfp); + while (!(err = read_list (key, country, &lnr))) + { + if (!strcmp (key, fpr)) + break; + } + if (gpg_err_code (err) == GPG_ERR_EOF) + err = gpg_error (GPG_ERR_NOT_FOUND); + + xfree (fpr); + return err; +} + + +/* We know that CERT is a qualified certificate. Ask the user for + consent to actually create a signature using this certificate. + Returns: 0 for yes, GPG_ERR_CANCEL for no or any otehr error + code. */ +gpg_error_t +gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert) +{ + gpg_error_t err; + char *name, *subject, *buffer, *p; + const char *s; + char *orig_codeset = NULL; + + name = ksba_cert_get_subject (cert, 0); + if (!name) + return gpg_error (GPG_ERR_GENERAL); + subject = gpgsm_format_name2 (name, 0); + ksba_free (name); name = NULL; + +#ifdef ENABLE_NLS + /* The Assuan agent protocol requires us to transmit utf-8 strings */ + orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL); +#ifdef HAVE_LANGINFO_CODESET + if (!orig_codeset) + orig_codeset = nl_langinfo (CODESET); +#endif + if (orig_codeset) + { /* We only switch when we are able to restore the codeset later. + Note that bind_textdomain_codeset does only return on memory + errors but not if a codeset is not available. Thus we don't + bother printing a diagnostic here. */ + orig_codeset = xstrdup (orig_codeset); + if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8")) + orig_codeset = NULL; + } +#endif + + if (asprintf (&name, + _("You are about to create a signature using your " + "certificate:\n" + "\"%s\"\n" + "This will create a qualified signature by law " + "equated to a handwritten signature.\n\n" + "Are you really sure that you want to do this?"), + subject? subject:"?" + ) < 0 ) + err = gpg_error_from_errno (errno); + else + err = 0; + +#ifdef ENABLE_NLS + if (orig_codeset) + bind_textdomain_codeset (PACKAGE_GT, orig_codeset); +#endif + xfree (orig_codeset); + xfree (subject); + + if (err) + return err; + + buffer = p = xtrymalloc (strlen (name) * 3 + 1); + if (!buffer) + { + err = gpg_error_from_errno (errno); + free (name); + return err; + } + for (s=name; *s; s++) + { + if (*s < ' ' || *s == '+') + { + sprintf (p, "%%%02X", *(unsigned char *)s); + p += 3; + } + else if (*s == ' ') + *p++ = '+'; + else + *p++ = *s; + } + *p = 0; + free (name); + + + err = gpgsm_agent_get_confirmation (ctrl, buffer); + + xfree (buffer); + return err; +} diff --git a/sm/sign.c b/sm/sign.c index 3230a0e98..d9a332c9a 100644 --- a/sm/sign.c +++ b/sm/sign.c @@ -426,6 +426,35 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, goto leave; } } + + + /* Check whether one of the certificates is qualified. Note that we + already validated the certificate and thus the user data stored + flag must be available. */ + for (cl=signerlist; cl; cl = cl->next) + { + size_t buflen; + char buffer[1]; + + err = ksba_cert_get_user_data (cl->cert, "is_qualified", + &buffer, sizeof (buffer), &buflen); + if (err || !buflen) + { + log_error (_("checking for qualified certificate failed: %s\n"), + gpg_strerror (err)); + rc = err; + goto leave; + } + if (*buffer) + { + err = gpgsm_qualified_consent (ctrl, cl->cert); + if (err) + { + rc = err; + goto leave; + } + } + } /* Prepare hashing (actually we are figuring out what we have set above)*/ rc = gcry_md_open (&data_md, 0, 0); @@ -443,6 +472,10 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist, if (!algo) { log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); + if (algoid + && ( !strcmp (algoid, "1.2.840.113549.1.1.2") + ||!strcmp (algoid, "1.2.840.113549.2.2"))) + log_info (_("(this is the MD2 algorithm)\n")); rc = gpg_error (GPG_ERR_BUG); goto leave; } diff --git a/sm/verify.c b/sm/verify.c index 410e86de7..f37cf4a75 100644 --- a/sm/verify.c +++ b/sm/verify.c @@ -179,8 +179,14 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) { algo = gcry_md_map_name (algoid); if (!algo) - log_error ("unknown hash algorithm `%s'\n", - algoid? algoid:"?"); + { + log_error ("unknown hash algorithm `%s'\n", + algoid? algoid:"?"); + if (algoid + && ( !strcmp (algoid, "1.2.840.113549.1.1.2") + ||!strcmp (algoid, "1.2.840.113549.2.2"))) + log_info (_("(this is the MD2 algorithm)\n")); + } else gcry_md_enable (data_md, algo); }