diff --git a/agent/ChangeLog b/agent/ChangeLog index d95b040df..fd24855bb 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,5 +1,12 @@ 2006-10-05 Werner Koch + * command.c (has_option_name): New. + (cmd_sethash): New --hash option. + * pksign.c (do_encode_raw_pkcs1): New. + (agent_pksign_do): Use it here for the TLS algo. + * agent.h (GCRY_MD_USER_TLS_MD5SHA1): New. + * divert-scd.c (pksign): Add case for tls-md5sha1. + * divert-scd.c (encode_md_for_card): Check that the algo is valid. 2006-10-04 Werner Koch diff --git a/agent/agent.h b/agent/agent.h index 3667e8149..cd5120721 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -36,8 +36,15 @@ #include "../common/errors.h" #include "membuf.h" +/* To convey some special hash algorithms we use algorithm numbers + reserved for application use. */ +#ifndef GCRY_MD_USER +#define GCRY_MD_USER 1024 +#endif +#define GCRY_MD_USER_TLS_MD5SHA1 (GCRY_MD_USER+1) -#define MAX_DIGEST_LEN 24 +/* Maximum length of a digest. */ +#define MAX_DIGEST_LEN 36 /* A large struct name "opt" to keep global flags */ struct diff --git a/agent/command.c b/agent/command.c index a8a701f95..a2634f7d5 100644 --- a/agent/command.c +++ b/agent/command.c @@ -136,6 +136,21 @@ has_option (const char *line, const char *name) return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); } +/* Same as has_option but does only test for the name of the option + and ignores an argument, i.e. with NAME being "--hash" it would + return true for "--hash" as well as for "--hash=foo". */ +static int +has_option_name (const char *line, const char *name) +{ + const char *s; + int n = strlen (name); + + s = strstr (line, name); + return (s && (s == line || spacep (s-1)) + && (!s[n] || spacep (s+n) || s[n] == '=')); +} + + /* Skip over options. It is assumed that leading spaces have been removed (this is the case for lines passed to a handler from assuan). Bkanls after the options are also removed. */ @@ -455,7 +470,7 @@ cmd_setkeydesc (assuan_context_t ctx, char *line) } -/* SETHASH +/* SETHASH --hash=| The client can use this command to tell the server about the data (which usually is a hash) to be signed. */ @@ -470,12 +485,37 @@ cmd_sethash (assuan_context_t ctx, char *line) char *endp; int algo; - /* Parse the algo number and check it. */ - algo = (int)strtoul (line, &endp, 10); - for (line = endp; *line == ' ' || *line == '\t'; line++) - ; - if (!algo || gcry_md_test_algo (algo)) - return set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL); + /* Parse the alternative hash options which may be used instead of + the algo number. */ + if (has_option_name (line, "--hash")) + { + if (has_option (line, "--hash=sha1")) + algo = GCRY_MD_SHA1; + else if (has_option (line, "--hash=sha256")) + algo = GCRY_MD_SHA256; + else if (has_option (line, "--hash=rmd160")) + algo = GCRY_MD_RMD160; + else if (has_option (line, "--hash=md5")) + algo = GCRY_MD_MD5; + else if (has_option (line, "--hash=tls-md5sha1")) + algo = GCRY_MD_USER_TLS_MD5SHA1; + else + return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); + } + else + algo = 0; + + line = skip_options (line); + + if (!algo) + { + /* No hash option has been given: require an algo number instead */ + algo = (int)strtoul (line, &endp, 10); + for (line = endp; *line == ' ' || *line == '\t'; line++) + ; + if (!algo || gcry_md_test_algo (algo)) + return set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL); + } ctrl->digest.algo = algo; /* Parse the hash value. */ @@ -483,8 +523,11 @@ cmd_sethash (assuan_context_t ctx, char *line) if (rc) return rc; n /= 2; - if (n != 16 && n != 20 && n != 24 && n != 32) + if (algo == GCRY_MD_USER_TLS_MD5SHA1 && n == 36) + ; + else if (n != 16 && n != 20 && n != 24 && n != 32) return set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash"); + if (n > MAX_DIGEST_LEN) return set_error (GPG_ERR_ASS_PARAMETER, "hash value to long"); diff --git a/agent/divert-scd.c b/agent/divert-scd.c index 89f177e64..67c9f4640 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -318,26 +318,36 @@ divert_pksign (ctrl_t ctrl, int rc; char *kid; size_t siglen; - unsigned char *sigval; - unsigned char *data; - size_t ndata; + unsigned char *sigval = NULL; rc = ask_for_card (ctrl, shadow_info, &kid); if (rc) return rc; - rc = encode_md_for_card (digest, digestlen, algo, - &data, &ndata); - if (rc) - return rc; + if (algo == GCRY_MD_USER_TLS_MD5SHA1) + { + rc = agent_card_pksign (ctrl, kid, getpin_cb, ctrl, + digest, digestlen, &sigval, &siglen); + } + else + { + unsigned char *data; + size_t ndata; + + rc = encode_md_for_card (digest, digestlen, algo, &data, &ndata); + if (!rc) + { + rc = agent_card_pksign (ctrl, kid, getpin_cb, ctrl, + data, ndata, &sigval, &siglen); + xfree (data); + } + } - rc = agent_card_pksign (ctrl, kid, getpin_cb, ctrl, - data, ndata, &sigval, &siglen); if (!rc) *r_sig = sigval; - xfree (data); + xfree (kid); - + return rc; } diff --git a/agent/pksign.c b/agent/pksign.c index 9863f9de0..c187eccae 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -39,7 +39,7 @@ do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash, gcry_sexp_t hash; int rc; - if (! raw_value) + if (!raw_value) { const char *s; char tmp[16+1]; @@ -55,7 +55,7 @@ do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash, rc = gcry_sexp_build (&hash, NULL, "(data (flags pkcs1) (hash %s %b))", - tmp, mdlen, md); + tmp, (int)mdlen, md); } else { @@ -77,6 +77,55 @@ do_encode_md (const byte * md, size_t mdlen, int algo, gcry_sexp_t * r_hash, } +/* Special version of do_encode_md to take care of pckcs#1 padding. + For TLS-MD5SHA1 we need to do the padding ourself as Libgrypt does + not know about this special scheme. Fixme: We should have a + pkcs1-only-padding flag for Libgcrypt. */ +static int +do_encode_raw_pkcs1 (const byte *md, size_t mdlen, unsigned int nbits, + gcry_sexp_t *r_hash) +{ + int rc; + gcry_sexp_t hash; + unsigned char *frame; + size_t i, n, nframe; + + nframe = (nbits+7) / 8; + if ( !mdlen || mdlen + 8 + 4 > nframe ) + { + /* Can't encode this hash into a frame of size NFRAME. */ + return gpg_error (GPG_ERR_TOO_SHORT); + } + + frame = xtrymalloc (nframe); + if (!frame) + return gpg_error_from_syserror (); + + /* Assemble the pkcs#1 block type 1. */ + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* Block type. */ + i = nframe - mdlen - 3 ; + assert (i >= 8); /* At least 8 bytes of padding. */ + memset (frame+n, 0xff, i ); + n += i; + frame[n++] = 0; + memcpy (frame+n, md, mdlen ); + n += mdlen; + assert (n == nframe); + + /* Create the S-expression. */ + rc = gcry_sexp_build (&hash, NULL, + "(data (flags raw) (value %b))", + (int)nframe, frame); + xfree (frame); + + *r_hash = hash; + return rc; +} + + + /* SIGN whatever information we have accumulated in CTRL and return the signature S-Expression. */ int @@ -133,12 +182,18 @@ agent_pksign_do (ctrl_t ctrl, const char *desc_text, gcry_sexp_t s_hash = NULL; - /* put the hash into a sexp */ - rc = do_encode_md (ctrl->digest.value, - ctrl->digest.valuelen, - ctrl->digest.algo, - &s_hash, - ctrl->digest.raw_value); + /* Put the hash into a sexp */ + if (ctrl->digest.algo == GCRY_MD_USER_TLS_MD5SHA1) + rc = do_encode_raw_pkcs1 (ctrl->digest.value, + ctrl->digest.valuelen, + gcry_pk_get_nbits (s_skey), + &s_hash); + else + rc = do_encode_md (ctrl->digest.value, + ctrl->digest.valuelen, + ctrl->digest.algo, + &s_hash, + ctrl->digest.raw_value); if (rc) goto leave;