diff --git a/agent/protect.c b/agent/protect.c index bc44fe107..1084ee208 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -1,1842 +1,1843 @@ /* protect.c - Un/Protect a secret key * Copyright (C) 1998-2003, 2007, 2009, 2011 Free Software Foundation, Inc. * Copyright (C) 1998-2003, 2007, 2009, 2011, 2013-2015 Werner Koch * * 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 #include #ifdef HAVE_W32_SYSTEM # ifdef HAVE_WINSOCK2_H # include # endif # include #else # include #endif #include "agent.h" #include "cvt-openpgp.h" #include "../common/sexp-parse.h" #include "../common/openpgpdefs.h" /* For s2k functions. */ /* The protection mode for encryption. The supported modes for decryption are listed in agent_unprotect(). */ #define PROT_CIPHER GCRY_CIPHER_AES128 #define PROT_CIPHER_STRING "aes" #define PROT_CIPHER_KEYLEN (128/8) /* A table containing the information needed to create a protected private key. */ static const struct { const char *algo; const char *parmlist; int prot_from, prot_to; int ecc_hack; } protect_info[] = { { "rsa", "nedpqu", 2, 5 }, { "dsa", "pqgyx", 4, 4 }, { "elg", "pgyx", 3, 3 }, { "ecdsa","pabgnqd", 6, 6, 1 }, { "ecdh", "pabgnqd", 6, 6, 1 }, { "ecc", "pabgnqd", 6, 6, 1 }, { NULL } }; /* The number of milliseconds we use in the S2K function and the * calibrated count value. A count value of zero indicates that the * calibration has not yet been done or needs to be done again. */ static unsigned int s2k_calibration_time = AGENT_S2K_CALIBRATION; static unsigned long s2k_calibrated_count; /* A helper object for time measurement. */ struct calibrate_time_s { #ifdef HAVE_W32_SYSTEM FILETIME creation_time, exit_time, kernel_time, user_time; #else clock_t ticks; #endif }; static int hash_passphrase (const char *passphrase, int hashalgo, int s2kmode, const unsigned char *s2ksalt, unsigned long s2kcount, unsigned char *key, size_t keylen); /* * Determine if we can use clock_gettime with CLOCK_THREAD_CPUTIME_ID, * at compile time. */ #if defined (CLOCK_THREAD_CPUTIME_ID) # if _POSIX_THREAD_CPUTIME > 0 # define USE_CLOCK_GETTIME 1 # elif _POSIX_THREAD_CPUTIME == 0 /* * In this case, we should check sysconf with _POSIX_THREAD_CPUTIME at * run time. As heuristics, for system with newer GNU C library, we * can assume it is available. */ # if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 17 # define USE_CLOCK_GETTIME 1 # endif # endif #else #undef USE_CLOCK_GETTIME #endif /* Get the process time and store it in DATA. */ static void calibrate_get_time (struct calibrate_time_s *data) { #ifdef HAVE_W32_SYSTEM GetProcessTimes (GetCurrentProcess (), &data->creation_time, &data->exit_time, &data->kernel_time, &data->user_time); #elif defined (USE_CLOCK_GETTIME) struct timespec tmp; clock_gettime (CLOCK_THREAD_CPUTIME_ID, &tmp); data->ticks = (clock_t)(((unsigned long long)tmp.tv_sec * 1000000000 + tmp.tv_nsec) * CLOCKS_PER_SEC / 1000000000); #else data->ticks = clock (); #endif } static unsigned long calibrate_elapsed_time (struct calibrate_time_s *starttime) { struct calibrate_time_s stoptime; calibrate_get_time (&stoptime); #ifdef HAVE_W32_SYSTEM { unsigned long long t1, t2; t1 = (((unsigned long long)starttime->kernel_time.dwHighDateTime << 32) + starttime->kernel_time.dwLowDateTime); t1 += (((unsigned long long)starttime->user_time.dwHighDateTime << 32) + starttime->user_time.dwLowDateTime); t2 = (((unsigned long long)stoptime.kernel_time.dwHighDateTime << 32) + stoptime.kernel_time.dwLowDateTime); t2 += (((unsigned long long)stoptime.user_time.dwHighDateTime << 32) + stoptime.user_time.dwLowDateTime); return (unsigned long)((t2 - t1)/10000); } #else return (unsigned long)((((double) (stoptime.ticks - starttime->ticks)) /CLOCKS_PER_SEC)*1000); #endif } /* Run a test hashing for COUNT and return the time required in milliseconds. */ static unsigned long calibrate_s2k_count_one (unsigned long count) { int rc; char keybuf[PROT_CIPHER_KEYLEN]; struct calibrate_time_s starttime; calibrate_get_time (&starttime); rc = hash_passphrase ("123456789abcdef0", GCRY_MD_SHA1, 3, "saltsalt", count, keybuf, sizeof keybuf); if (rc) BUG (); return calibrate_elapsed_time (&starttime); } /* Measure the time we need to do the hash operations and deduce an S2K count which requires roughly some targeted amount of time. */ static unsigned long calibrate_s2k_count (void) { unsigned long count; unsigned long ms; for (count = 65536; count; count *= 2) { ms = calibrate_s2k_count_one (count); if (opt.verbose > 1) log_info ("S2K calibration: %lu -> %lums\n", count, ms); if (ms > s2k_calibration_time) break; } count = (unsigned long)(((double)count / ms) * s2k_calibration_time); count /= 1024; count *= 1024; if (count < 65536) count = 65536; if (opt.verbose) { ms = calibrate_s2k_count_one (count); log_info ("S2K calibration: %lu -> %lums\n", count, ms); } return count; } /* Set the calibration time. This may be called early at startup or * at any time. Thus it should one set variables. */ void set_s2k_calibration_time (unsigned int milliseconds) { if (!milliseconds) milliseconds = AGENT_S2K_CALIBRATION; else if (milliseconds > 60 * 1000) milliseconds = 60 * 1000; /* Cap at 60 seconds. */ s2k_calibration_time = milliseconds; s2k_calibrated_count = 0; /* Force re-calibration. */ } /* Return the calibrated S2K count. This is only public for the use * of the Assuan getinfo s2k_count_cal command. */ unsigned long get_calibrated_s2k_count (void) { if (!s2k_calibrated_count) s2k_calibrated_count = calibrate_s2k_count (); /* Enforce a lower limit. */ return s2k_calibrated_count < 65536 ? 65536 : s2k_calibrated_count; } /* Return the standard S2K count. */ unsigned long get_standard_s2k_count (void) { if (opt.s2k_count) return opt.s2k_count < 65536 ? 65536 : opt.s2k_count; return get_calibrated_s2k_count (); } /* Return the milliseconds required for the standard S2K * operation. */ unsigned long get_standard_s2k_time (void) { return calibrate_s2k_count_one (get_standard_s2k_count ()); } /* Same as get_standard_s2k_count but return the count in the encoding as described by rfc4880. */ unsigned char get_standard_s2k_count_rfc4880 (void) { unsigned long iterations; unsigned int count; unsigned char result; unsigned char c=0; iterations = get_standard_s2k_count (); if (iterations >= 65011712) return 255; /* Need count to be in the range 16-31 */ for (count=iterations>>6; count>=32; count>>=1) c++; result = (c<<4)|(count-16); if (S2K_DECODE_COUNT(result) < iterations) result++; return result; } /* Calculate the MIC for a private key or shared secret S-expression. SHA1HASH should point to a 20 byte buffer. This function is suitable for all algorithms. */ static gpg_error_t calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash) { const unsigned char *hash_begin, *hash_end; const unsigned char *s; size_t n; int is_shared_secret; s = plainkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "private-key")) is_shared_secret = 0; else if (smatch (&s, n, "shared-secret")) is_shared_secret = 1; else return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); hash_begin = s; if (!is_shared_secret) { s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; /* Skip the algorithm name. */ } while (*s == '(') { s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; if ( *s != ')' ) return gpg_error (GPG_ERR_INV_SEXP); s++; } if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); s++; hash_end = s; gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, hash_begin, hash_end - hash_begin); return 0; } /* Encrypt the parameter block starting at PROTBEGIN with length PROTLEN using the utf8 encoded key PASSPHRASE and return the entire encrypted block in RESULT or return with an error code. SHA1HASH is the 20 byte SHA-1 hash required for the integrity code. The parameter block is expected to be an incomplete canonical encoded S-Expression of the form (example in advanced format): (d #046129F..[some bytes not shown]..81#) (p #00e861b..[some bytes not shown]..f1#) (q #00f7a7c..[some bytes not shown]..61#) (u #304559a..[some bytes not shown]..9b#) the returned block is the S-Expression: (protected mode (parms) encrypted_octet_string) */ static int do_encryption (const unsigned char *hashbegin, size_t hashlen, const unsigned char *protbegin, size_t protlen, const char *passphrase, const char *timestamp_exp, size_t timestamp_exp_len, unsigned char **result, size_t *resultlen, unsigned long s2k_count, int use_ocb) { gcry_cipher_hd_t hd; const char *modestr; unsigned char hashvalue[20]; int blklen, enclen, outlen; unsigned char *iv = NULL; unsigned int ivsize; /* Size of the buffer allocated for IV. */ const unsigned char *s2ksalt; /* Points into IV. */ int rc; char *outbuf = NULL; char *p; int saltpos, ivpos, encpos; s2ksalt = iv; /* Silence compiler warning. */ *resultlen = 0; *result = NULL; modestr = (use_ocb? "openpgp-s2k3-ocb-aes" /* */: "openpgp-s2k3-sha1-" PROT_CIPHER_STRING "-cbc"); rc = gcry_cipher_open (&hd, PROT_CIPHER, use_ocb? GCRY_CIPHER_MODE_OCB : GCRY_CIPHER_MODE_CBC, GCRY_CIPHER_SECURE); if (rc) return rc; /* We need to work on a copy of the data because this makes it * easier to add the trailer and the padding and more important we * have to prefix the text with 2 parenthesis. In CBC mode we * have to allocate enough space for: * * (()(4:hash4:sha120:)) + padding * * we always append a full block of random bytes as padding but * encrypt only what is needed for a full blocksize. In OCB mode we * have to allocate enough space for just: * * (()) */ blklen = gcry_cipher_get_algo_blklen (PROT_CIPHER); if (use_ocb) { /* (( )) */ outlen = 2 + protlen + 2 ; enclen = outlen + 16 /* taglen */; outbuf = gcry_malloc_secure (enclen); } else { /* (( )( 4:hash 4:sha1 20: )) */ outlen = 2 + protlen + 2 + 6 + 6 + 23 + 2 + blklen; enclen = outlen/blklen * blklen; outbuf = gcry_malloc_secure (outlen); } if (!outbuf) { rc = out_of_core (); goto leave; } /* Allocate a buffer for the nonce and the salt. */ if (!rc) { /* Allocate random bytes to be used as IV, padding and s2k salt * or in OCB mode for a nonce and the s2k salt. The IV/nonce is * set later because for OCB we need to set the key first. */ ivsize = (use_ocb? 12 : (blklen*2)) + 8; iv = xtrymalloc (ivsize); if (!iv) rc = gpg_error_from_syserror (); else { gcry_create_nonce (iv, ivsize); s2ksalt = iv + ivsize - 8; } } /* Hash the passphrase and set the key. */ if (!rc) { unsigned char *key; size_t keylen = PROT_CIPHER_KEYLEN; key = gcry_malloc_secure (keylen); if (!key) rc = out_of_core (); else { rc = hash_passphrase (passphrase, GCRY_MD_SHA1, 3, s2ksalt, s2k_count? s2k_count:get_standard_s2k_count(), key, keylen); if (!rc) rc = gcry_cipher_setkey (hd, key, keylen); xfree (key); } } if (rc) goto leave; /* Set the IV/nonce. */ rc = gcry_cipher_setiv (hd, iv, use_ocb? 12 : blklen); if (rc) goto leave; if (use_ocb) { /* In OCB Mode we use only the public key parameters as AAD. */ rc = gcry_cipher_authenticate (hd, hashbegin, protbegin - hashbegin); if (!rc) rc = gcry_cipher_authenticate (hd, timestamp_exp, timestamp_exp_len); if (!rc) rc = gcry_cipher_authenticate (hd, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin)); } else { /* Hash the entire expression for CBC mode. Because * TIMESTAMP_EXP won't get protected, we can't simply hash a * continuous buffer but need to call md_write several times. */ gcry_md_hd_t md; rc = gcry_md_open (&md, GCRY_MD_SHA1, 0 ); if (!rc) { gcry_md_write (md, hashbegin, protbegin - hashbegin); gcry_md_write (md, protbegin, protlen); gcry_md_write (md, timestamp_exp, timestamp_exp_len); gcry_md_write (md, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin)); memcpy (hashvalue, gcry_md_read (md, GCRY_MD_SHA1), 20); gcry_md_close (md); } } /* Encrypt. */ if (!rc) { p = outbuf; *p++ = '('; *p++ = '('; memcpy (p, protbegin, protlen); p += protlen; if (use_ocb) { *p++ = ')'; *p++ = ')'; } else { memcpy (p, ")(4:hash4:sha120:", 17); p += 17; memcpy (p, hashvalue, 20); p += 20; *p++ = ')'; *p++ = ')'; memcpy (p, iv+blklen, blklen); /* Add padding. */ p += blklen; } log_assert ( p - outbuf == outlen); if (use_ocb) { gcry_cipher_final (hd); rc = gcry_cipher_encrypt (hd, outbuf, outlen, NULL, 0); if (!rc) { log_assert (outlen + 16 == enclen); rc = gcry_cipher_gettag (hd, outbuf + outlen, 16); } } else { rc = gcry_cipher_encrypt (hd, outbuf, enclen, NULL, 0); } } if (rc) goto leave; /* Release cipher handle and check for errors. */ gcry_cipher_close (hd); /* Now allocate the buffer we want to return. This is (protected openpgp-s2k3-sha1-aes-cbc ((sha1 salt no_of_iterations) 16byte_iv) encrypted_octet_string) in canoncical format of course. We use asprintf and %n modifier and dummy values as placeholders. */ { char countbuf[35]; snprintf (countbuf, sizeof countbuf, "%lu", s2k_count ? s2k_count : get_standard_s2k_count ()); p = xtryasprintf ("(9:protected%d:%s((4:sha18:%n_8bytes_%u:%s)%d:%n%*s)%d:%n%*s)", (int)strlen (modestr), modestr, &saltpos, (unsigned int)strlen (countbuf), countbuf, use_ocb? 12 : blklen, &ivpos, use_ocb? 12 : blklen, "", enclen, &encpos, enclen, ""); if (!p) { gpg_error_t tmperr = out_of_core (); xfree (iv); xfree (outbuf); return tmperr; } } *resultlen = strlen (p); *result = (unsigned char*)p; memcpy (p+saltpos, s2ksalt, 8); memcpy (p+ivpos, iv, use_ocb? 12 : blklen); memcpy (p+encpos, outbuf, enclen); xfree (iv); xfree (outbuf); return 0; leave: gcry_cipher_close (hd); xfree (iv); xfree (outbuf); return rc; } /* Protect the key encoded in canonical format in PLAINKEY. We assume a valid S-Exp here. With USE_UCB set to -1 the default scheme is used (ie. either CBC or OCB), set to 0 the old CBC mode is used, and set to 1 OCB is used. */ int agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char **result, size_t *resultlen, unsigned long s2k_count, int use_ocb) { int rc; const char *parmlist; int prot_from_idx, prot_to_idx; const unsigned char *s; const unsigned char *hash_begin, *hash_end; const unsigned char *prot_begin, *prot_end, *real_end; size_t n; int c, infidx, i; char timestamp_exp[35]; unsigned char *protected; size_t protectedlen; int depth = 0; unsigned char *p; int have_curve = 0; if (use_ocb == -1) use_ocb = !!opt.enable_extended_key_format; /* Create an S-expression with the protected-at timestamp. */ memcpy (timestamp_exp, "(12:protected-at15:", 19); gnupg_get_isotime (timestamp_exp+19); timestamp_exp[19+15] = ')'; /* Parse original key. */ s = plainkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "private-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); depth++; hash_begin = s; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); for (infidx=0; protect_info[infidx].algo && !smatch (&s, n, protect_info[infidx].algo); infidx++) ; if (!protect_info[infidx].algo) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); /* The parser below is a complete mess: To make it robust for ECC use we should reorder the s-expression to include only what we really need and thus guarantee the right order for saving stuff. This should be done before calling this function and maybe with the help of the new gcry_sexp_extract_param. */ parmlist = protect_info[infidx].parmlist; prot_from_idx = protect_info[infidx].prot_from; prot_to_idx = protect_info[infidx].prot_to; prot_begin = prot_end = NULL; for (i=0; (c=parmlist[i]); i++) { if (i == prot_from_idx) prot_begin = s; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (n != 1 || c != *s) { if (n == 5 && !memcmp (s, "curve", 5) && !i && protect_info[infidx].ecc_hack) { /* This is a private ECC key but the first parameter is the name of the curve. We change the parameter list here to the one we expect in this case. */ have_curve = 1; parmlist = "?qd"; prot_from_idx = 2; prot_to_idx = 2; } else if (n == 5 && !memcmp (s, "flags", 5) && i == 1 && have_curve) { /* "curve" followed by "flags": Change again. */ parmlist = "??qd"; prot_from_idx = 3; prot_to_idx = 3; } else return gpg_error (GPG_ERR_INV_SEXP); } s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s +=n; /* skip value */ if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); depth--; if (i == prot_to_idx) prot_end = s; s++; } if (*s != ')' || !prot_begin || !prot_end ) return gpg_error (GPG_ERR_INV_SEXP); depth--; hash_end = s; s++; /* Skip to the end of the S-expression. */ log_assert (depth == 1); rc = sskip (&s, &depth); if (rc) return rc; log_assert (!depth); real_end = s-1; rc = do_encryption (hash_begin, hash_end - hash_begin + 1, prot_begin, prot_end - prot_begin + 1, passphrase, timestamp_exp, sizeof (timestamp_exp), &protected, &protectedlen, s2k_count, use_ocb); if (rc) return rc; /* Now create the protected version of the key. Note that the 10 extra bytes are for the inserted "protected-" string (the beginning of the plaintext reads: "((11:private-key(" ). The 35 term is the space for (12:protected-at15:). */ *resultlen = (10 + (prot_begin-plainkey) + protectedlen + 35 + (real_end-prot_end)); *result = p = xtrymalloc (*resultlen); if (!p) { gpg_error_t tmperr = out_of_core (); xfree (protected); return tmperr; } memcpy (p, "(21:protected-", 14); p += 14; memcpy (p, plainkey+4, prot_begin - plainkey - 4); p += prot_begin - plainkey - 4; memcpy (p, protected, protectedlen); p += protectedlen; memcpy (p, timestamp_exp, 35); p += 35; memcpy (p, prot_end+1, real_end - prot_end); p += real_end - prot_end; log_assert ( p - *result == *resultlen); xfree (protected); return 0; } /* Do the actual decryption and check the return list for consistency. */ static gpg_error_t do_decryption (const unsigned char *aad_begin, size_t aad_len, const unsigned char *aadhole_begin, size_t aadhole_len, const unsigned char *protected, size_t protectedlen, const char *passphrase, const unsigned char *s2ksalt, unsigned long s2kcount, const unsigned char *iv, size_t ivlen, int prot_cipher, int prot_cipher_keylen, int is_ocb, unsigned char **result) { int rc; int blklen; gcry_cipher_hd_t hd; unsigned char *outbuf; size_t reallen; blklen = gcry_cipher_get_algo_blklen (prot_cipher); if (is_ocb) { /* OCB does not require a multiple of the block length but we * check that it is long enough for the 128 bit tag and that we * have the 96 bit nonce. */ if (protectedlen < (4 + 16) || ivlen != 12) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); } else { if (protectedlen < 4 || (protectedlen%blklen)) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); } rc = gcry_cipher_open (&hd, prot_cipher, is_ocb? GCRY_CIPHER_MODE_OCB : GCRY_CIPHER_MODE_CBC, GCRY_CIPHER_SECURE); if (rc) return rc; outbuf = gcry_malloc_secure (protectedlen); if (!outbuf) rc = out_of_core (); /* Hash the passphrase and set the key. */ if (!rc) { unsigned char *key; key = gcry_malloc_secure (prot_cipher_keylen); if (!key) rc = out_of_core (); else { rc = hash_passphrase (passphrase, GCRY_MD_SHA1, 3, s2ksalt, s2kcount, key, prot_cipher_keylen); if (!rc) rc = gcry_cipher_setkey (hd, key, prot_cipher_keylen); xfree (key); } } /* Set the IV/nonce. */ if (!rc) { rc = gcry_cipher_setiv (hd, iv, ivlen); } /* Decrypt. */ if (!rc) { if (is_ocb) { rc = gcry_cipher_authenticate (hd, aad_begin, aadhole_begin - aad_begin); if (!rc) rc = gcry_cipher_authenticate (hd, aadhole_begin + aadhole_len, aad_len - (aadhole_begin+aadhole_len - aad_begin)); if (!rc) { gcry_cipher_final (hd); rc = gcry_cipher_decrypt (hd, outbuf, protectedlen - 16, protected, protectedlen - 16); } if (!rc) { rc = gcry_cipher_checktag (hd, protected + protectedlen - 16, 16); if (gpg_err_code (rc) == GPG_ERR_CHECKSUM) { /* Return Bad Passphrase instead of checksum error */ rc = gpg_error (GPG_ERR_BAD_PASSPHRASE); } } } else { rc = gcry_cipher_decrypt (hd, outbuf, protectedlen, protected, protectedlen); } } /* Release cipher handle and check for errors. */ gcry_cipher_close (hd); if (rc) { xfree (outbuf); return rc; } /* Do a quick check on the data structure. */ if (*outbuf != '(' && outbuf[1] != '(') { xfree (outbuf); return gpg_error (GPG_ERR_BAD_PASSPHRASE); } /* Check that we have a consistent S-Exp. */ reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL); if (!reallen || (reallen + blklen < protectedlen) ) { xfree (outbuf); return gpg_error (GPG_ERR_BAD_PASSPHRASE); } *result = outbuf; return 0; } /* Merge the parameter list contained in CLEARTEXT with the original * protect lists PROTECTEDKEY by replacing the list at REPLACEPOS. * Return the new list in RESULT and the MIC value in the 20 byte * buffer SHA1HASH; if SHA1HASH is NULL no MIC will be computed. * CUTOFF and CUTLEN will receive the offset and the length of the * resulting list which should go into the MIC calculation but then be * removed. */ static gpg_error_t merge_lists (const unsigned char *protectedkey, size_t replacepos, const unsigned char *cleartext, unsigned char *sha1hash, unsigned char **result, size_t *resultlen, size_t *cutoff, size_t *cutlen) { size_t n, newlistlen; unsigned char *newlist, *p; const unsigned char *s; const unsigned char *startpos, *endpos; int i, rc; *result = NULL; *resultlen = 0; *cutoff = 0; *cutlen = 0; if (replacepos < 26) return gpg_error (GPG_ERR_BUG); /* Estimate the required size of the resulting list. We have a large safety margin of >20 bytes (FIXME: MIC hash from CLEARTEXT and the removed "protected-" */ newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL); if (!newlistlen) return gpg_error (GPG_ERR_BUG); n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL); if (!n) return gpg_error (GPG_ERR_BUG); newlistlen += n; newlist = gcry_malloc_secure (newlistlen); if (!newlist) return out_of_core (); /* Copy the initial segment */ strcpy ((char*)newlist, "(11:private-key"); p = newlist + 15; memcpy (p, protectedkey+15+10, replacepos-15-10); p += replacepos-15-10; /* Copy the cleartext. */ s = cleartext; if (*s != '(' && s[1] != '(') { xfree (newlist); return gpg_error (GPG_ERR_BUG); /*we already checked this */ } s += 2; startpos = s; while ( *s == '(' ) { s++; n = snext (&s); if (!n) goto invalid_sexp; s += n; n = snext (&s); if (!n) goto invalid_sexp; s += n; if ( *s != ')' ) goto invalid_sexp; s++; } if ( *s != ')' ) goto invalid_sexp; endpos = s; s++; /* Intermezzo: Get the MIC if requested. */ if (sha1hash) { if (*s != '(') goto invalid_sexp; s++; n = snext (&s); if (!smatch (&s, n, "hash")) goto invalid_sexp; n = snext (&s); if (!smatch (&s, n, "sha1")) goto invalid_sexp; n = snext (&s); if (n != 20) goto invalid_sexp; memcpy (sha1hash, s, 20); s += n; if (*s != ')') goto invalid_sexp; } /* Append the parameter list. */ memcpy (p, startpos, endpos - startpos); p += endpos - startpos; /* Skip over the protected list element in the original list. */ s = protectedkey + replacepos; log_assert (*s == '('); s++; i = 1; rc = sskip (&s, &i); if (rc) goto failure; /* Record the position of the optional protected-at expression. */ if (*s == '(') { const unsigned char *save_s = s; s++; n = snext (&s); if (smatch (&s, n, "protected-at")) { i = 1; rc = sskip (&s, &i); if (rc) goto failure; *cutlen = s - save_s; } s = save_s; } startpos = s; i = 2; /* we are inside this level */ rc = sskip (&s, &i); if (rc) goto failure; log_assert (s[-1] == ')'); endpos = s; /* one behind the end of the list */ /* Append the rest. */ if (*cutlen) *cutoff = p - newlist; memcpy (p, startpos, endpos - startpos); p += endpos - startpos; /* ready */ *result = newlist; *resultlen = newlistlen; return 0; failure: wipememory (newlist, newlistlen); xfree (newlist); return rc; invalid_sexp: wipememory (newlist, newlistlen); xfree (newlist); return gpg_error (GPG_ERR_INV_SEXP); } /* Unprotect the key encoded in canonical format. We assume a valid S-Exp here. If a protected-at item is available, its value will be stored at protected_at unless this is NULL. */ gpg_error_t agent_unprotect (ctrl_t ctrl, const unsigned char *protectedkey, const char *passphrase, gnupg_isotime_t protected_at, unsigned char **result, size_t *resultlen) { static const struct { const char *name; /* Name of the protection method. */ int algo; /* (A zero indicates the "openpgp-native" hack.) */ int keylen; /* Used key length in bytes. */ unsigned int is_ocb:1; } algotable[] = { { "openpgp-s2k3-sha1-aes-cbc", GCRY_CIPHER_AES128, (128/8)}, { "openpgp-s2k3-sha1-aes256-cbc", GCRY_CIPHER_AES256, (256/8)}, { "openpgp-s2k3-ocb-aes", GCRY_CIPHER_AES128, (128/8), 1}, { "openpgp-native", 0, 0 } }; int rc; const unsigned char *s; const unsigned char *protect_list; size_t n; int infidx, i; unsigned char sha1hash[20], sha1hash2[20]; const unsigned char *s2ksalt; unsigned long s2kcount; const unsigned char *iv; int prot_cipher, prot_cipher_keylen; int is_ocb; const unsigned char *aad_begin, *aad_end, *aadhole_begin, *aadhole_end; const unsigned char *prot_begin; unsigned char *cleartext; unsigned char *final; size_t finallen; size_t cutoff, cutlen; if (protected_at) *protected_at = 0; s = protectedkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "protected-private-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); { aad_begin = aad_end = s; aad_end++; i = 1; rc = sskip (&aad_end, &i); if (rc) return rc; } s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); for (infidx=0; protect_info[infidx].algo && !smatch (&s, n, protect_info[infidx].algo); infidx++) ; if (!protect_info[infidx].algo) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); /* See whether we have a protected-at timestamp. */ protect_list = s; /* Save for later. */ if (protected_at) { while (*s == '(') { prot_begin = s; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "protected-at")) { n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (n != 15) return gpg_error (GPG_ERR_UNKNOWN_SEXP); memcpy (protected_at, s, 15); protected_at[15] = 0; break; } s += n; i = 1; rc = sskip (&s, &i); if (rc) return rc; } } /* Now find the list with the protected information. Here is an example for such a list: (protected openpgp-s2k3-sha1-aes-cbc ((sha1 ) ) ) */ s = protect_list; for (;;) { if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); prot_begin = s; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "protected")) break; s += n; i = 1; rc = sskip (&s, &i); if (rc) return rc; } /* found */ { aadhole_begin = aadhole_end = prot_begin; aadhole_end++; i = 1; rc = sskip (&aadhole_end, &i); if (rc) return rc; } n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); /* Lookup the protection algo. */ prot_cipher = 0; /* (avoid gcc warning) */ prot_cipher_keylen = 0; /* (avoid gcc warning) */ is_ocb = 0; for (i=0; i < DIM (algotable); i++) if (smatch (&s, n, algotable[i].name)) { prot_cipher = algotable[i].algo; prot_cipher_keylen = algotable[i].keylen; is_ocb = algotable[i].is_ocb; break; } if (i == DIM (algotable)) return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION); if (!prot_cipher) /* This is "openpgp-native". */ { gcry_sexp_t s_prot_begin; rc = gcry_sexp_sscan (&s_prot_begin, NULL, prot_begin, gcry_sexp_canon_len (prot_begin, 0,NULL,NULL)); if (rc) return rc; rc = convert_from_openpgp_native (ctrl, s_prot_begin, passphrase, &final); gcry_sexp_release (s_prot_begin); if (!rc) { *result = final; *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL); } return rc; } if (*s != '(' || s[1] != '(') return gpg_error (GPG_ERR_INV_SEXP); s += 2; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "sha1")) return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION); n = snext (&s); if (n != 8) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); s2ksalt = s; s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); /* We expect a list close as next, so we can simply use strtoul() here. We might want to check that we only have digits - but this is nothing we should worry about */ if (s[n] != ')' ) return gpg_error (GPG_ERR_INV_SEXP); /* Old versions of gpg-agent used the funny floating point number in a byte encoding as specified by OpenPGP. However this is not needed and thus we now store it as a plain unsigned integer. We can easily distinguish the old format by looking at its value: Less than 256 is an old-style encoded number; other values are plain integers. In any case we check that they are at least 65536 because we never used a lower value in the past and we should have a lower limit. */ s2kcount = strtoul ((const char*)s, NULL, 10); if (!s2kcount) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); if (s2kcount < 256) s2kcount = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6); if (s2kcount < 65536) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); s += n; s++; /* skip list end */ n = snext (&s); if (is_ocb) { if (n != 12) /* Wrong size of the nonce. */ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); } else { if (n != 16) /* Wrong blocksize for IV (we support only 128 bit). */ return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); } iv = s; s += n; if (*s != ')' ) return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); cleartext = NULL; /* Avoid cc warning. */ rc = do_decryption (aad_begin, aad_end - aad_begin, aadhole_begin, aadhole_end - aadhole_begin, s, n, passphrase, s2ksalt, s2kcount, iv, is_ocb? 12:16, prot_cipher, prot_cipher_keylen, is_ocb, &cleartext); if (rc) return rc; rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext, is_ocb? NULL : sha1hash, &final, &finallen, &cutoff, &cutlen); /* Albeit cleartext has been allocated in secure memory and thus xfree will wipe it out, we do an extra wipe just in case somethings goes badly wrong. */ wipememory (cleartext, n); xfree (cleartext); if (rc) return rc; if (!is_ocb) { rc = calculate_mic (final, sha1hash2); if (!rc && memcmp (sha1hash, sha1hash2, 20)) rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION); if (rc) { wipememory (final, finallen); xfree (final); return rc; } } /* Now remove the part which is included in the MIC but should not go into the final thing. */ if (cutlen) { memmove (final+cutoff, final+cutoff+cutlen, finallen-cutoff-cutlen); finallen -= cutlen; } *result = final; *resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL); return 0; } /* Check the type of the private key, this is one of the constants: PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the value 0), PRIVATE_KEY_CLEAR for an unprotected private key. PRIVATE_KEY_PROTECTED for an protected private key or PRIVATE_KEY_SHADOWED for a sub key where the secret parts are stored elsewhere. Finally PRIVATE_KEY_OPENPGP_NONE may be returned is the key is still in the openpgp-native format but without protection. */ int agent_private_key_type (const unsigned char *privatekey) { const unsigned char *s; size_t n; int i; s = privatekey; if (*s != '(') return PRIVATE_KEY_UNKNOWN; s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; if (smatch (&s, n, "protected-private-key")) { /* We need to check whether this is openpgp-native protected with the protection method "none". In that case we return a different key type so that the caller knows that there is no need to ask for a passphrase. */ if (*s != '(') return PRIVATE_KEY_PROTECTED; /* Unknown sexp - assume protected. */ s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ s += n; /* Skip over the algo */ /* Find the (protected ...) list. */ for (;;) { if (*s != '(') return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ if (smatch (&s, n, "protected")) break; s += n; i = 1; if (sskip (&s, &i)) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ } /* Found - Is this openpgp-native? */ n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ if (smatch (&s, n, "openpgp-native")) /* Yes. */ { if (*s != '(') return PRIVATE_KEY_UNKNOWN; /* Unknown sexp. */ s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ s += n; /* Skip over "openpgp-private-key". */ /* Find the (protection ...) list. */ for (;;) { if (*s != '(') return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ s++; n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ if (smatch (&s, n, "protection")) break; s += n; i = 1; if (sskip (&s, &i)) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ } /* Found - Is the mode "none"? */ n = snext (&s); if (!n) return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */ if (smatch (&s, n, "none")) return PRIVATE_KEY_OPENPGP_NONE; /* Yes. */ } return PRIVATE_KEY_PROTECTED; } if (smatch (&s, n, "shadowed-private-key")) return PRIVATE_KEY_SHADOWED; if (smatch (&s, n, "private-key")) return PRIVATE_KEY_CLEAR; return PRIVATE_KEY_UNKNOWN; } /* Transform a passphrase into a suitable key of length KEYLEN and store this key in the caller provided buffer KEY. The caller must provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on that mode an S2KSALT of 8 random bytes and an S2KCOUNT. Returns an error code on failure. */ static int hash_passphrase (const char *passphrase, int hashalgo, int s2kmode, const unsigned char *s2ksalt, unsigned long s2kcount, unsigned char *key, size_t keylen) { /* The key derive function does not support a zero length string for the passphrase in the S2K modes. Return a better suited error code than GPG_ERR_INV_DATA. */ if (!passphrase || !*passphrase) return gpg_error (GPG_ERR_NO_PASSPHRASE); return gcry_kdf_derive (passphrase, strlen (passphrase), s2kmode == 3? GCRY_KDF_ITERSALTED_S2K : s2kmode == 1? GCRY_KDF_SALTED_S2K : s2kmode == 0? GCRY_KDF_SIMPLE_S2K : GCRY_KDF_NONE, hashalgo, s2ksalt, 8, s2kcount, keylen, key); } gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo, int s2kmode, const unsigned char *s2ksalt, unsigned int s2kcount, unsigned char *key, size_t keylen) { return hash_passphrase (passphrase, hashalgo, s2kmode, s2ksalt, S2K_DECODE_COUNT (s2kcount), key, keylen); } /* Create an canonical encoded S-expression with the shadow info from a card's SERIALNO and the IDSTRING. */ unsigned char * make_shadow_info (const char *serialno, const char *idstring) { const char *s; char *info, *p; char numbuf[20]; size_t n; for (s=serialno, n=0; *s && s[1]; s += 2) n++; info = p = xtrymalloc (1 + sizeof numbuf + n + sizeof numbuf + strlen (idstring) + 1 + 1); if (!info) return NULL; *p++ = '('; p = stpcpy (p, smklen (numbuf, sizeof numbuf, n, NULL)); for (s=serialno; *s && s[1]; s += 2) *(unsigned char *)p++ = xtoi_2 (s); p = stpcpy (p, smklen (numbuf, sizeof numbuf, strlen (idstring), NULL)); p = stpcpy (p, idstring); *p++ = ')'; *p = 0; return (unsigned char *)info; } /* Create a shadow key from a public key. We use the shadow protocol "t1-v1" and insert the S-expressionn SHADOW_INFO. The resulting S-expression is returned in an allocated buffer RESULT will point to. The input parameters are expected to be valid canonicalized S-expressions */ int agent_shadow_key_type (const unsigned char *pubkey, const unsigned char *shadow_info, const unsigned char *type, unsigned char **result) { const unsigned char *s; const unsigned char *point; size_t n; int depth = 0; char *p; size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL); size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL); if (!pubkey_len || !shadow_info_len) return gpg_error (GPG_ERR_INV_VALUE); s = pubkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "public-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; /* skip over the algorithm name */ while (*s != ')') { if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s +=n; /* skip value */ if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); depth--; s++; } point = s; /* insert right before the point */ depth--; s++; log_assert (depth == 1); /* Calculate required length by taking in account: the "shadowed-" prefix, the "shadowed", shadow type as well as some parenthesis */ n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1; *result = xtrymalloc (n); p = (char*)*result; if (!p) return out_of_core (); p = stpcpy (p, "(20:shadowed-private-key"); /* (10:public-key ...)*/ memcpy (p, pubkey+14, point - (pubkey+14)); p += point - (pubkey+14); p += sprintf (p, "(8:shadowed%d:%s", (int)strlen(type), type); memcpy (p, shadow_info, shadow_info_len); p += shadow_info_len; *p++ = ')'; memcpy (p, point, pubkey_len - (point - pubkey)); p += pubkey_len - (point - pubkey); return 0; } int agent_shadow_key (const unsigned char *pubkey, const unsigned char *shadow_info, unsigned char **result) { return agent_shadow_key_type (pubkey, shadow_info, "t1-v1", result); } /* Parse a canonical encoded shadowed key and return a pointer to the inner list with the shadow_info and the shadow type */ gpg_error_t agent_get_shadow_info_type (const unsigned char *shadowkey, unsigned char const **shadow_info, unsigned char **shadow_type) { const unsigned char *s, *saved_s; size_t n, saved_n; int depth = 0; + (void)depth; s = shadowkey; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (!smatch (&s, n, "shadowed-private-key")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_UNKNOWN_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s += n; /* skip over the algorithm name */ for (;;) { if (*s == ')') return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); depth++; s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (smatch (&s, n, "shadowed")) break; s += n; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); s +=n; /* skip value */ if (*s != ')') return gpg_error (GPG_ERR_INV_SEXP); depth--; s++; } /* Found the shadowed list, S points to the protocol */ n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); saved_s = s; saved_n = n; if (smatch (&s, n, "t1-v1") || smatch(&s, n, "tpm2-v1")) { if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); if (shadow_info) *shadow_info = s; } else return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); s = saved_s; n = saved_n; if (shadow_type) { char *buf = xtrymalloc(n+1); if (!buf) return gpg_error_from_syserror (); memcpy (buf, s, n); buf[n] = '\0'; *shadow_type = buf; } return 0; } gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey, unsigned char const **shadow_info) { return agent_get_shadow_info_type (shadowkey, shadow_info, NULL); } int agent_is_tpm2_key (gcry_sexp_t s_skey) { unsigned char *buf; unsigned char *type; size_t len; gpg_error_t err; err = make_canon_sexp (s_skey, &buf, &len); if (err) return 0; err = agent_get_shadow_info_type (buf, NULL, &type); xfree (buf); if (err) return 0; err = strcmp (type, "tpm2-v1") == 0; xfree (type); return err; } gpg_error_t agent_get_shadow_type (const unsigned char *shadowkey, unsigned char **shadow_type) { return agent_get_shadow_info_type (shadowkey, NULL, shadow_type); } /* Parse the canonical encoded SHADOW_INFO S-expression. On success the hex encoded serial number is returned as a malloced strings at R_HEXSN and the Id string as a malloced string at R_IDSTR. On error an error code is returned and NULL is stored at the result parameters addresses. If the serial number or the ID string is not required, NULL may be passed for them. Note that R_PINLEN is currently not used by any caller. */ gpg_error_t parse_shadow_info (const unsigned char *shadow_info, char **r_hexsn, char **r_idstr, int *r_pinlen) { const unsigned char *s; size_t n; if (r_hexsn) *r_hexsn = NULL; if (r_idstr) *r_idstr = NULL; if (r_pinlen) *r_pinlen = 0; s = shadow_info; if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); s++; n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); if (r_hexsn) { *r_hexsn = bin2hex (s, n, NULL); if (!*r_hexsn) return gpg_error_from_syserror (); } s += n; n = snext (&s); if (!n) { if (r_hexsn) { xfree (*r_hexsn); *r_hexsn = NULL; } return gpg_error (GPG_ERR_INV_SEXP); } if (r_idstr) { *r_idstr = xtrymalloc (n+1); if (!*r_idstr) { if (r_hexsn) { xfree (*r_hexsn); *r_hexsn = NULL; } return gpg_error_from_syserror (); } memcpy (*r_idstr, s, n); (*r_idstr)[n] = 0; } /* Parse the optional PINLEN. */ n = snext (&s); if (!n) return 0; if (r_pinlen) { char *tmpstr = xtrymalloc (n+1); if (!tmpstr) { if (r_hexsn) { xfree (*r_hexsn); *r_hexsn = NULL; } if (r_idstr) { xfree (*r_idstr); *r_idstr = NULL; } return gpg_error_from_syserror (); } memcpy (tmpstr, s, n); tmpstr[n] = 0; *r_pinlen = (int)strtol (tmpstr, NULL, 10); xfree (tmpstr); } return 0; } diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c index 2e0859861..a591a2b5a 100644 --- a/dirmngr/crlfetch.c +++ b/dirmngr/crlfetch.c @@ -1,594 +1,594 @@ /* crlfetch.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB * Copyright (C) 2003, 2004, 2005, 2006, 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, see . */ #include #include #include #include #include "crlfetch.h" #include "dirmngr.h" #include "misc.h" #include "http.h" #include "ks-engine.h" /* For ks_http_fetch. */ #if USE_LDAP # include "ldap-wrapper.h" #endif /* For detecting armored CRLs received via HTTP (yes, such CRLS really exits, e.g. http://grid.fzk.de/ca/gridka-crl.pem at least in June 2008) we need a context in the reader callback. */ struct reader_cb_context_s { estream_t fp; /* The stream used with the ksba reader. */ int checked:1; /* PEM/binary detection ahs been done. */ int is_pem:1; /* The file stream is PEM encoded. */ struct b64state b64state; /* The state used for Base64 decoding. */ }; /* We need to associate a reader object with the reader callback context. This table is used for it. */ struct file_reader_map_s { ksba_reader_t reader; struct reader_cb_context_s *cb_ctx; }; #define MAX_FILE_READER 50 static struct file_reader_map_s file_reader_map[MAX_FILE_READER]; /* Associate FP with READER. If the table is full wait until another thread has removed an entry. */ static void register_file_reader (ksba_reader_t reader, struct reader_cb_context_s *cb_ctx) { int i; for (;;) { for (i=0; i < MAX_FILE_READER; i++) if (!file_reader_map[i].reader) { file_reader_map[i].reader = reader; file_reader_map[i].cb_ctx = cb_ctx; return; } log_info (_("reader to file mapping table full - waiting\n")); gnupg_sleep (2); } } /* Scan the table for an entry matching READER, remove that entry and return the associated file pointer. */ static struct reader_cb_context_s * get_file_reader (ksba_reader_t reader) { struct reader_cb_context_s *cb_ctx = NULL; int i; for (i=0; i < MAX_FILE_READER; i++) if (file_reader_map[i].reader == reader) { cb_ctx = file_reader_map[i].cb_ctx; file_reader_map[i].reader = NULL; file_reader_map[i].cb_ctx = NULL; break; } return cb_ctx; } static int my_es_read (void *opaque, char *buffer, size_t nbytes, size_t *nread) { struct reader_cb_context_s *cb_ctx = opaque; int result; result = es_read (cb_ctx->fp, buffer, nbytes, nread); if (result) return result; /* Fixme we should check whether the semantics of es_read are okay and well defined. I have some doubts. */ if (nbytes && !*nread && es_feof (cb_ctx->fp)) return gpg_error (GPG_ERR_EOF); if (!nread && es_ferror (cb_ctx->fp)) return gpg_error (GPG_ERR_EIO); if (!cb_ctx->checked && *nread) { int c = *(unsigned char *)buffer; cb_ctx->checked = 1; if ( ((c & 0xc0) >> 6) == 0 /* class: universal */ && (c & 0x1f) == 16 /* sequence */ && (c & 0x20) /* is constructed */ ) ; /* Binary data. */ else { cb_ctx->is_pem = 1; b64dec_start (&cb_ctx->b64state, ""); } } if (cb_ctx->is_pem && *nread) { size_t nread2; if (b64dec_proc (&cb_ctx->b64state, buffer, *nread, &nread2)) { /* EOF from decoder. */ *nread = 0; result = gpg_error (GPG_ERR_EOF); } else *nread = nread2; } return result; } /* For now we do not support LDAP over Tor. */ static gpg_error_t no_crl_due_to_tor (ctrl_t ctrl) { gpg_error_t err = gpg_error (GPG_ERR_NOT_SUPPORTED); const char *text = _("CRL access not possible due to Tor mode"); log_error ("%s", text); dirmngr_status_printf (ctrl, "NOTE", "no_crl_due_to_tor %u %s", err, text); return gpg_error (GPG_ERR_NOT_SUPPORTED); } /* Fetch CRL from URL and return the entire CRL using new ksba reader object in READER. Note that this reader object should be closed only using ldap_close_reader. */ gpg_error_t crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader) { gpg_error_t err; parsed_uri_t uri; estream_t httpfp = NULL; *reader = NULL; if (!url) return gpg_error (GPG_ERR_INV_ARG); err = http_parse_uri (&uri, url, 0); http_release_parsed_uri (uri); if (!err) /* Yes, our HTTP code groks that. */ { if (opt.disable_http) { log_error (_("CRL access not possible due to disabled %s\n"), "HTTP"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else { /* Note that we also allow root certificates loaded from * "/etc/gnupg/trusted-certs/". We also do not consult the * CRL for the TLS connection - that may lead to a loop. * Due to cacert.org redirecting their https URL to http we * also allow such a downgrade. */ err = ks_http_fetch (ctrl, url, (KS_HTTP_FETCH_TRUST_CFG | KS_HTTP_FETCH_NO_CRL | KS_HTTP_FETCH_ALLOW_DOWNGRADE ), &httpfp); } if (err) log_error (_("error retrieving '%s': %s\n"), url, gpg_strerror (err)); else { struct reader_cb_context_s *cb_ctx; cb_ctx = xtrycalloc (1, sizeof *cb_ctx); if (!cb_ctx) err = gpg_error_from_syserror (); else if (!(err = ksba_reader_new (reader))) { cb_ctx->fp = httpfp; err = ksba_reader_set_cb (*reader, &my_es_read, cb_ctx); if (!err) { /* The ksba reader misses a user pointer thus we * need to come up with our own way of associating a * file pointer (or well the callback context) with * the reader. It is only required when closing the * reader thus there is no performance issue doing * it this way. FIXME: We now have a close * notification which might be used here. */ register_file_reader (*reader, cb_ctx); httpfp = NULL; } } if (err) { log_error (_("error initializing reader object: %s\n"), gpg_strerror (err)); ksba_reader_release (*reader); *reader = NULL; xfree (cb_ctx); } } } else /* Let the LDAP code parse other schemes. */ { if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else if (dirmngr_use_tor ()) { err = no_crl_due_to_tor (ctrl); } else { # if USE_LDAP err = url_fetch_ldap (ctrl, url, reader); # else /*!USE_LDAP*/ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); # endif /*!USE_LDAP*/ } } es_fclose (httpfp); return err; } /* Fetch CRL for ISSUER using a default server. Return the entire CRL as a newly opened stream returned in R_FP. */ gpg_error_t crl_fetch_default (ctrl_t ctrl, const char *issuer, ksba_reader_t *reader) { if (dirmngr_use_tor ()) { return no_crl_due_to_tor (ctrl); } if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } #if USE_LDAP return attr_fetch_ldap (ctrl, issuer, "certificateRevocationList", reader); #else (void)ctrl; (void)issuer; (void)reader; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } /* Fetch a CA certificate for DN using the default server. This * function only initiates the fetch; fetch_next_cert must be used to * actually read the certificate; end_cert_fetch to end the * operation. */ gpg_error_t ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, const char *dn) { if (dirmngr_use_tor ()) { return no_crl_due_to_tor (ctrl); } if (opt.disable_ldap) { log_error (_("CRL access not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } #if USE_LDAP return start_cacert_fetch_ldap (ctrl, context, dn); #else (void)ctrl; (void)context; (void)dn; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } gpg_error_t start_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, strlist_t patterns, const ldap_server_t server) { if (dirmngr_use_tor ()) { return no_crl_due_to_tor (ctrl); } if (opt.disable_ldap) { log_error (_("certificate search not possible due to disabled %s\n"), "LDAP"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } #if USE_LDAP return start_cert_fetch_ldap (ctrl, context, patterns, server); #else (void)ctrl; (void)context; (void)patterns; (void)server; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } gpg_error_t fetch_next_cert (cert_fetch_context_t context, unsigned char **value, size_t * valuelen) { #if USE_LDAP return fetch_next_cert_ldap (context, value, valuelen); #else (void)context; (void)value; (void)valuelen; return gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif } /* Fetch the next data from CONTEXT, assuming it is a certificate and return * it as a cert object in R_CERT. */ gpg_error_t fetch_next_ksba_cert (cert_fetch_context_t context, ksba_cert_t *r_cert) { gpg_error_t err; - unsigned char *value; - size_t valuelen; + unsigned char *value = NULL; + size_t valuelen = 0; ksba_cert_t cert; *r_cert = NULL; #if USE_LDAP err = fetch_next_cert_ldap (context, &value, &valuelen); if (!err && !value) err = gpg_error (GPG_ERR_BUG); #else (void)context; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif if (err) return err; err = ksba_cert_new (&cert); if (err) { xfree (value); return err; } err = ksba_cert_init_from_mem (cert, value, valuelen); xfree (value); if (err) { ksba_cert_release (cert); return err; } *r_cert = cert; return 0; } void end_cert_fetch (cert_fetch_context_t context) { #if USE_LDAP end_cert_fetch_ldap (context); #else (void)context; #endif } /* Read a certificate from an HTTP URL and return it as an estream * memory buffer at R_FP. */ static gpg_error_t read_cert_via_http (ctrl_t ctrl, const char *url, estream_t *r_fp) { gpg_error_t err; estream_t fp = NULL; estream_t httpfp = NULL; size_t nread, nwritten; char buffer[1024]; if ((err = ks_http_fetch (ctrl, url, KS_HTTP_FETCH_TRUST_CFG, &httpfp))) goto leave; /* We now read the data from the web server into a memory buffer. * To DOS we limit the certificate length to 32k. */ fp = es_fopenmem (32*1024, "rw"); if (!fp) { err = gpg_error_from_syserror (); log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); goto leave; } for (;;) { if (es_read (httpfp, buffer, sizeof buffer, &nread)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", es_fname_get (httpfp), gpg_strerror (err)); goto leave; } if (!nread) break; /* Ready. */ if (es_write (fp, buffer, nread, &nwritten)) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", es_fname_get (fp), gpg_strerror (err)); goto leave; } else if (nread != nwritten) { err = gpg_error (GPG_ERR_EIO); log_error ("error writing '%s': %s\n", es_fname_get (fp), "short write"); goto leave; } } es_rewind (fp); *r_fp = fp; fp = NULL; leave: es_fclose (httpfp); es_fclose (fp); return err; } /* Lookup a cert by it's URL. */ gpg_error_t fetch_cert_by_url (ctrl_t ctrl, const char *url, unsigned char **value, size_t *valuelen) { const unsigned char *cert_image = NULL; size_t cert_image_n; ksba_reader_t reader = NULL; ksba_cert_t cert = NULL; gpg_error_t err; *value = NULL; *valuelen = 0; err = ksba_cert_new (&cert); if (err) goto leave; if (url && (!strncmp (url, "http:", 5) || !strncmp (url, "https:", 6))) { estream_t stream; void *der; size_t derlen; err = read_cert_via_http (ctrl, url, &stream); if (err) goto leave; if (es_fclose_snatch (stream, &der, &derlen)) { err = gpg_error_from_syserror (); goto leave; } err = ksba_cert_init_from_mem (cert, der, derlen); xfree (der); if (err) goto leave; } else /* Assume LDAP. */ { #if USE_LDAP err = url_fetch_ldap (ctrl, url, &reader); #else (void)ctrl; (void)url; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); #endif /*USE_LDAP*/ if (err) goto leave; err = ksba_cert_read_der (cert, reader); if (err) goto leave; } cert_image = ksba_cert_get_image (cert, &cert_image_n); if (!cert_image || !cert_image_n) { err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto leave; } *value = xtrymalloc (cert_image_n); if (!*value) { err = gpg_error_from_syserror (); goto leave; } memcpy (*value, cert_image, cert_image_n); *valuelen = cert_image_n; leave: ksba_cert_release (cert); #if USE_LDAP ldap_wrapper_release_context (reader); #endif /*USE_LDAP*/ return err; } /* This function is to be used to close the reader object. In addition to running ksba_reader_release it also releases the LDAP or HTTP contexts associated with that reader. */ void crl_close_reader (ksba_reader_t reader) { struct reader_cb_context_s *cb_ctx; if (!reader) return; /* Check whether this is a HTTP one. */ cb_ctx = get_file_reader (reader); if (cb_ctx) { /* This is an HTTP context. */ if (cb_ctx->fp) es_fclose (cb_ctx->fp); /* Release the base64 decoder state. */ if (cb_ctx->is_pem) b64dec_finish (&cb_ctx->b64state); /* Release the callback context. */ xfree (cb_ctx); } else /* This is an ldap wrapper context (Currently not used). */ { #if USE_LDAP ldap_wrapper_release_context (reader); #endif /*USE_LDAP*/ } /* Now get rid of the reader object. */ ksba_reader_release (reader); } diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index 57cf04a7e..45d0b0b13 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -1,447 +1,445 @@ /* ks-action.c - OpenPGP keyserver actions * Copyright (C) 2011 Free Software Foundation, Inc. * Copyright (C) 2011, 2014 Werner Koch * Copyright (C) 2015 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 . */ #include #include #include #include #include #include "dirmngr.h" #include "misc.h" #include "ks-engine.h" #include "ks-action.h" #if USE_LDAP # include "ldap-parse-uri.h" #endif /* Called by the engine's help functions to print the actual help. */ gpg_error_t ks_print_help (ctrl_t ctrl, const char *text) { return dirmngr_status_help (ctrl, text); } /* Called by the engine's help functions to print the actual help. */ gpg_error_t ks_printf_help (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; } /* Run the help command for the engine responsible for URI. */ gpg_error_t ks_action_help (ctrl_t ctrl, const char *url) { gpg_error_t err; parsed_uri_t parsed_uri; /* The broken down URI. */ - char *tmpstr; - const char *s; if (!url || !*url) { ks_print_help (ctrl, "Known schemata:\n"); parsed_uri = NULL; } else { #if USE_LDAP if (!strncmp (url, "ldap:", 5) && !(url[5] == '/' && url[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:", url+5, NULL); if (!tmpstr) err = gpg_error_from_syserror (); else { err = http_parse_uri (&parsed_uri, tmpstr, 0); xfree (tmpstr); } } else if ((s=strchr (url, ':')) && !(s[1] == '/' && s[2] == '/')) { /* No scheme given. Use http_parse_uri to put the string as * opaque value into parsed_uri. */ tmpstr = strconcat ("opaque:", url, NULL); if (!tmpstr) err = gpg_error_from_syserror (); else { err = http_parse_uri (&parsed_uri, tmpstr, 0); xfree (tmpstr); } } else if (ldap_uri_p (url)) err = ldap_parse_uri (&parsed_uri, url); else #endif { err = http_parse_uri (&parsed_uri, url, HTTP_PARSE_NO_SCHEME_CHECK); } if (err) return err; } /* Call all engines to give them a chance to print a help string. */ err = ks_hkp_help (ctrl, parsed_uri); if (!err) err = ks_http_help (ctrl, parsed_uri); if (!err) err = ks_finger_help (ctrl, parsed_uri); if (!err) err = ks_kdns_help (ctrl, parsed_uri); #if USE_LDAP if (!err) err = ks_ldap_help (ctrl, parsed_uri); #endif if (!parsed_uri) ks_print_help (ctrl, "(Use an URL for engine specific help.)"); else http_release_parsed_uri (parsed_uri); return err; } /* Resolve all host names. This is useful for looking at the status of configured keyservers. */ gpg_error_t ks_action_resolve (ctrl_t ctrl, uri_item_t keyservers) { gpg_error_t err = 0; int any_server = 0; uri_item_t uri; for (uri = keyservers; !err && uri; uri = uri->next) { if (uri->parsed_uri->is_http) { any_server = 1; err = ks_hkp_resolve (ctrl, uri->parsed_uri); if (err) break; } } if (!any_server) err = gpg_error (GPG_ERR_NO_KEYSERVER); return err; } /* Search all configured keyservers for keys matching PATTERNS and write the result to the provided output stream. */ gpg_error_t ks_action_search (ctrl_t ctrl, uri_item_t keyservers, strlist_t patterns, estream_t outfp) { gpg_error_t err = 0; int any_server = 0; int any_results = 0; uri_item_t uri; estream_t infp; if (!patterns) return gpg_error (GPG_ERR_NO_USER_ID); /* FIXME: We only take care of the first pattern. To fully support multiple patterns we might either want to run several queries in parallel and merge them. We also need to decide what to do with errors - it might not be the best idea to ignore an error from one server and silently continue with another server. For now we stop at the first error, unless the server responds with '404 Not Found', in which case we try the next server. */ for (uri = keyservers; !err && uri; uri = uri->next) { int is_http = uri->parsed_uri->is_http; int is_ldap = 0; unsigned int http_status = 0; #if USE_LDAP is_ldap = (!strcmp (uri->parsed_uri->scheme, "ldap") || !strcmp (uri->parsed_uri->scheme, "ldaps") || !strcmp (uri->parsed_uri->scheme, "ldapi") || uri->parsed_uri->opaque); #endif if (is_http || is_ldap) { any_server = 1; #if USE_LDAP if (is_ldap) err = ks_ldap_search (ctrl, uri->parsed_uri, patterns->d, &infp); else #endif { err = ks_hkp_search (ctrl, uri->parsed_uri, patterns->d, &infp, &http_status); } if (err == gpg_error (GPG_ERR_NO_DATA) && http_status == 404 /* not found */) { /* No record found. Clear error and try next server. */ err = 0; continue; } if (!err) { err = copy_stream (infp, outfp); es_fclose (infp); any_results = 1; break; } } } if (!any_server) err = gpg_error (GPG_ERR_NO_KEYSERVER); else if (err == 0 && !any_results) err = gpg_error (GPG_ERR_NO_DATA); return err; } /* Get the requested keys (matching PATTERNS) using all configured keyservers and write the result to the provided output stream. */ gpg_error_t ks_action_get (ctrl_t ctrl, uri_item_t keyservers, strlist_t patterns, int ldap_only, estream_t outfp) { gpg_error_t err = 0; gpg_error_t first_err = 0; int any_server = 0; int any_data = 0; strlist_t sl; uri_item_t uri; estream_t infp; if (!patterns) return gpg_error (GPG_ERR_NO_USER_ID); /* FIXME: We only take care of the first keyserver. To fully support multiple keyservers we need to track the result for each pattern and use the next keyserver if one key was not found. The keyservers might not all be fully synced thus it is not clear whether the first keyserver has the freshest copy of the key. Need to think about a better strategy. */ for (uri = keyservers; !err && uri; uri = uri->next) { int is_hkp_s = (strcmp (uri->parsed_uri->scheme, "hkp") == 0 || strcmp (uri->parsed_uri->scheme, "hkps") == 0); int is_http_s = (strcmp (uri->parsed_uri->scheme, "http") == 0 || strcmp (uri->parsed_uri->scheme, "https") == 0); int is_ldap = 0; if (ldap_only) is_hkp_s = is_http_s = 0; #if USE_LDAP is_ldap = (!strcmp (uri->parsed_uri->scheme, "ldap") || !strcmp (uri->parsed_uri->scheme, "ldaps") || !strcmp (uri->parsed_uri->scheme, "ldapi") || uri->parsed_uri->opaque); #endif if (is_hkp_s || is_http_s || is_ldap) { any_server = 1; for (sl = patterns; !err && sl; sl = sl->next) { #if USE_LDAP if (is_ldap) err = ks_ldap_get (ctrl, uri->parsed_uri, sl->d, &infp); else #endif if (is_hkp_s) err = ks_hkp_get (ctrl, uri->parsed_uri, sl->d, &infp); else if (is_http_s) err = ks_http_fetch (ctrl, uri->parsed_uri->original, KS_HTTP_FETCH_NOCACHE, &infp); else BUG (); if (err) { /* It is possible that a server does not carry a key, thus we only save the error and continue with the next pattern. FIXME: It is an open question how to return such an error condition to the caller. */ first_err = err; err = 0; } else { err = copy_stream (infp, outfp); /* Reading from the keyserver should never fail, thus return this error. */ if (!err) any_data = 1; es_fclose (infp); infp = NULL; } } } if (any_data) break; /* Stop loop after a keyserver returned something. */ } if (!any_server) err = gpg_error (GPG_ERR_NO_KEYSERVER); else if (!err && first_err && !any_data) err = first_err; return err; } /* Retrieve keys from URL and write the result to the provided output * stream OUTFP. If OUTFP is NULL the data is written to the bit * bucket. */ gpg_error_t ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp) { gpg_error_t err = 0; estream_t infp; parsed_uri_t parsed_uri; /* The broken down URI. */ if (!url) return gpg_error (GPG_ERR_INV_URI); err = http_parse_uri (&parsed_uri, url, HTTP_PARSE_NO_SCHEME_CHECK); if (err) return err; if (parsed_uri->is_http) { err = ks_http_fetch (ctrl, url, KS_HTTP_FETCH_NOCACHE, &infp); if (!err) { err = copy_stream (infp, outfp); es_fclose (infp); } } else if (!parsed_uri->opaque) { err = gpg_error (GPG_ERR_INV_URI); } else if (!strcmp (parsed_uri->scheme, "finger")) { err = ks_finger_fetch (ctrl, parsed_uri, &infp); if (!err) { err = copy_stream (infp, outfp); es_fclose (infp); } } else if (!strcmp (parsed_uri->scheme, "kdns")) { err = ks_kdns_fetch (ctrl, parsed_uri, &infp); if (!err) { err = copy_stream (infp, outfp); es_fclose (infp); } } else err = gpg_error (GPG_ERR_INV_URI); http_release_parsed_uri (parsed_uri); return err; } /* Send an OpenPGP key to all keyservers. The key in {DATA,DATALEN} is expected to be in OpenPGP binary transport format. The metadata in {INFO,INFOLEN} is in colon-separated format (concretely, it is the output of 'gpg --list-keys --with-colons KEYID'). This function may modify DATA and INFO. If this is a problem, then the caller should create a copy. */ gpg_error_t ks_action_put (ctrl_t ctrl, uri_item_t keyservers, void *data, size_t datalen, void *info, size_t infolen) { gpg_error_t err = 0; gpg_error_t first_err = 0; int any_server = 0; uri_item_t uri; (void) info; (void) infolen; for (uri = keyservers; !err && uri; uri = uri->next) { int is_http = uri->parsed_uri->is_http; int is_ldap = 0; #if USE_LDAP is_ldap = (!strcmp (uri->parsed_uri->scheme, "ldap") || !strcmp (uri->parsed_uri->scheme, "ldaps") || !strcmp (uri->parsed_uri->scheme, "ldapi") || uri->parsed_uri->opaque); #endif if (is_http || is_ldap) { any_server = 1; #if USE_LDAP if (is_ldap) err = ks_ldap_put (ctrl, uri->parsed_uri, data, datalen, info, infolen); else #endif { err = ks_hkp_put (ctrl, uri->parsed_uri, data, datalen); } if (err) { first_err = err; err = 0; } } } if (!any_server) err = gpg_error (GPG_ERR_NO_KEYSERVER); else if (!err && first_err) err = first_err; return err; } diff --git a/dirmngr/server.c b/dirmngr/server.c index 1d9cb7fb4..2928a2596 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -1,3220 +1,3219 @@ /* 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" /* (ks_hkp_print_hosttable) */ #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) { /* 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); 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; 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); if (!opt.allow_ocsp) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder); 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; - const char *s; char *tmpstr = NULL; *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] {}\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"; 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; int ldap_only; if (has_option (line, "--quick")) ctrl->timeout = opt.connect_quick_timeout; ldap_only = has_option (line, "--ldap"); 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; } } 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, ldap_only, 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 { 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/dirmngr/validate.c b/dirmngr/validate.c index 55cf9b1e7..231600ff6 100644 --- a/dirmngr/validate.c +++ b/dirmngr/validate.c @@ -1,1339 +1,1340 @@ /* validate.c - Validate a certificate chain. * Copyright (C) 2001, 2003, 2004, 2008 Free Software Foundation, Inc. * Copyright (C) 2004, 2006, 2008, 2017 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 #include "dirmngr.h" #include "certcache.h" #include "crlcache.h" #include "validate.h" #include "misc.h" /* Mode parameters for cert_check_usage(). */ enum cert_usage_modes { CERT_USAGE_MODE_SIGN, /* Usable for encryption. */ CERT_USAGE_MODE_ENCR, /* Usable for signing. */ CERT_USAGE_MODE_VRFY, /* Usable for verification. */ CERT_USAGE_MODE_DECR, /* Usable for decryption. */ CERT_USAGE_MODE_CERT, /* Usable for cert signing. */ CERT_USAGE_MODE_OCSP, /* Usable for OCSP respone signing. */ CERT_USAGE_MODE_CRL /* Usable for CRL signing. */ }; /* While running the validation function we need to keep track of the certificates and the validation outcome of each. We use this type for it. */ struct chain_item_s { struct chain_item_s *next; ksba_cert_t cert; /* The certificate. */ unsigned char fpr[20]; /* Fingerprint of the certificate. */ int is_self_signed; /* This certificate is self-signed. */ int is_valid; /* The certifiate is valid except for revocations. */ }; typedef struct chain_item_s *chain_item_t; /* A couple of constants with Object Identifiers. */ static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1"; static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2"; static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3"; static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4"; static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8"; static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9"; /* Prototypes. */ static gpg_error_t check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert); /* Make sure that the values defined in the headers are correct. We * can't use the preprocessor due to the use of enums. */ static void check_header_constants (void) { log_assert (CERTTRUST_CLASS_SYSTEM == VALIDATE_FLAG_TRUST_SYSTEM); log_assert (CERTTRUST_CLASS_CONFIG == VALIDATE_FLAG_TRUST_CONFIG); log_assert (CERTTRUST_CLASS_HKP == VALIDATE_FLAG_TRUST_HKP); log_assert (CERTTRUST_CLASS_HKPSPOOL == VALIDATE_FLAG_TRUST_HKPSPOOL); #undef X #define X (VALIDATE_FLAG_TRUST_SYSTEM | VALIDATE_FLAG_TRUST_CONFIG \ | VALIDATE_FLAG_TRUST_HKP | VALIDATE_FLAG_TRUST_HKPSPOOL) #if ( X & VALIDATE_FLAG_MASK_TRUST ) != X # error VALIDATE_FLAG_MASK_TRUST is bad #endif #if ( ~X & VALIDATE_FLAG_MASK_TRUST ) # error VALIDATE_FLAG_MASK_TRUST is bad #endif #undef X } /* Check whether CERT contains critical extensions we don't know about. */ static gpg_error_t unknown_criticals (ksba_cert_t cert) { static const char *known[] = { "2.5.29.15", /* keyUsage */ "2.5.29.19", /* basic Constraints */ "2.5.29.32", /* certificatePolicies */ "2.5.29.37", /* extendedKeyUsage */ NULL }; int i, idx, crit; const char *oid; int unsupported; strlist_t sl; gpg_error_t err, rc; rc = 0; 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) { log_error (_("critical certificate extension %s is not supported"), oid); rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT); } } if (err && gpg_err_code (err) != GPG_ERR_EOF) rc = err; /* Such an error takes precedence. */ return rc; } /* Basic check for supported policies. */ static gpg_error_t check_cert_policy (ksba_cert_t cert) { static const char *allowed[] = { "2.289.9.9", NULL }; gpg_error_t err; int idx; char *p, *haystack; char *policies; 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 certifiate 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) 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"); /* See whether we find ALLOWED (which is an OID) in POLICIES */ for (idx=0; allowed[idx]; idx++) { for (haystack=policies; (p=strstr (haystack, allowed[idx])); haystack = p+1) { if ( !(p == policies || p[-1] == '\n') ) continue; /* Does not match the begin of a line. */ if (p[strlen (allowed[idx])] != ':') continue; /* The length does not match. */ /* Yep - it does match: Return okay. */ ksba_free (policies); return 0; } } if (!any_critical) { log_info (_("Note: non-critical certificate policy not allowed")); err = 0; } else { log_info (_("certificate policy not allowed")); err = gpg_error (GPG_ERR_NO_POLICY_MATCH); } ksba_free (policies); return err; } static gpg_error_t allowed_ca (ksba_cert_t cert, int *chainlen) { gpg_error_t err; int flag; err = ksba_cert_is_ca (cert, &flag, chainlen); if (err) return err; if (!flag) { if (!is_trusted_cert (cert, CERTTRUST_CLASS_CONFIG)) { /* The German SigG Root CA's certificate does not flag itself as a CA; thus we relax this requirement if we trust a root CA. I think this is reasonable. Note, that gpgsm implements a far stricter scheme here but also features a "relax" flag in the trustlist.txt. */ if (chainlen) *chainlen = 3; /* That is what the SigG implements. */ if (opt.verbose) log_info (_("accepting root CA not marked as a CA")); } else { log_error (_("issuer certificate is not marked as a CA")); return gpg_error (GPG_ERR_BAD_CA_CERT); } } return 0; } /* Helper for validate_cert_chain. */ static gpg_error_t check_revocations (ctrl_t ctrl, chain_item_t chain) { gpg_error_t err = 0; int any_revoked = 0; int any_no_crl = 0; int any_crl_too_old = 0; chain_item_t ci; log_assert (ctrl->check_revocations_nest_level >= 0); log_assert (chain); if (ctrl->check_revocations_nest_level > 10) { log_error (_("CRL checking too deeply nested\n")); return gpg_error(GPG_ERR_BAD_CERT_CHAIN); } ctrl->check_revocations_nest_level++; for (ci=chain; ci; ci = ci->next) { assert (ci->cert); if (ci == chain) { /* It does not make sense to check the root certificate for revocations. In almost all cases this will lead to a catch-22 as the root certificate is the final trust anchor for the certificates and the CRLs. We expect the user to remove root certificates from the list of trusted certificates in case they have been revoked. */ if (opt.verbose) cert_log_name (_("not checking CRL for"), ci->cert); continue; } if (opt.verbose) cert_log_name (_("checking CRL for"), ci->cert); err = crl_cache_cert_isvalid (ctrl, ci->cert, 0); if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN) { err = crl_cache_reload_crl (ctrl, ci->cert); if (!err) err = crl_cache_cert_isvalid (ctrl, ci->cert, 0); } switch (gpg_err_code (err)) { case 0: err = 0; break; case GPG_ERR_CERT_REVOKED: any_revoked = 1; err = 0; break; case GPG_ERR_NO_CRL_KNOWN: any_no_crl = 1; err = 0; break; case GPG_ERR_CRL_TOO_OLD: any_crl_too_old = 1; err = 0; break; default: break; } } ctrl->check_revocations_nest_level--; if (err) ; else if (any_revoked) err = gpg_error (GPG_ERR_CERT_REVOKED); else if (any_no_crl) err = gpg_error (GPG_ERR_NO_CRL_KNOWN); else if (any_crl_too_old) err = gpg_error (GPG_ERR_CRL_TOO_OLD); else err = 0; return err; } /* Check whether CERT is a root certificate. ISSUERDN and SUBJECTDN are the DNs already extracted by the caller from CERT. Returns True if this is the case. */ 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; } /* Validate the certificate CHAIN up to the trust anchor. Optionally return the closest expiration time in R_EXPTIME (this is useful for caching issues). MODE is one of the VALIDATE_MODE_* constants. Note that VALIDATE_MODE_OCSP is not used due to the removal of the system service in 2.1.15. Instead only the callback to gpgsm to validate a certificate is used. If R_TRUST_ANCHOR is not NULL and the validation would fail only because the root certificate is not trusted, the hexified fingerprint of that root certificate is stored at R_TRUST_ANCHOR and success is returned. The caller needs to free the value at R_TRUST_ANCHOR; in all other cases NULL is stored there. */ gpg_error_t validate_cert_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, unsigned int flags, char **r_trust_anchor) { gpg_error_t err = 0; int depth, maxdepth; char *issuer = NULL; char *subject = NULL; ksba_cert_t subject_cert = NULL; ksba_cert_t issuer_cert = NULL; ksba_isotime_t current_time; ksba_isotime_t exptime; int any_expired = 0; int any_no_policy_match = 0; chain_item_t chain; check_header_constants (); if (r_exptime) *r_exptime = 0; *exptime = 0; if (r_trust_anchor) *r_trust_anchor = NULL; if (DBG_X509) dump_cert ("subject", cert); /* May the target certificate be used for this purpose? */ if ((flags & VALIDATE_FLAG_OCSP) && (err = check_cert_use_ocsp (cert))) return err; if ((flags & VALIDATE_FLAG_CRL) && (err = check_cert_use_crl (cert))) return err; /* If we already validated the certificate not too long ago, we can avoid the excessive computations and lookups unless the caller asked for the expiration time. */ if (!r_exptime) { size_t buflen; time_t validated_at; err = ksba_cert_get_user_data (cert, "validated_at", &validated_at, sizeof (validated_at), &buflen); if (err || buflen != sizeof (validated_at) || !validated_at) err = 0; /* Not available or other error. */ else { /* If the validation is not older than 30 minutes we are ready. */ if (validated_at < gnupg_get_time () + (30*60)) { if (opt.verbose) log_info ("certificate is good (cached)\n"); /* Note, that we can't jump to leave here as this would falsely updated the validation timestamp. */ return 0; } } } /* Get the current time. */ gnupg_get_isotime (current_time); /* We walk up the chain until we find a trust anchor. */ subject_cert = cert; maxdepth = 10; /* Sensible limit on the length of the chain. */ chain = NULL; depth = 0; for (;;) { /* Get the subject and issuer name from the current certificate. */ ksba_free (issuer); ksba_free (subject); issuer = ksba_cert_get_issuer (subject_cert, 0); subject = ksba_cert_get_subject (subject_cert, 0); if (!issuer) { log_error (_("no issuer found in certificate\n")); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } /* Handle the notBefore and notAfter timestamps. */ { 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) { log_error (_("certificate with invalid validity: %s"), gpg_strerror (err)); err = gpg_error (GPG_ERR_BAD_CERT); goto leave; } /* Keep track of the nearest expiration time in EXPTIME. */ if (*not_after) { if (!*exptime) gnupg_copy_time (exptime, not_after); else if (strcmp (not_after, exptime) < 0 ) gnupg_copy_time (exptime, not_after); } /* Check whether the certificate is already valid. */ if (*not_before && strcmp (current_time, not_before) < 0 ) { log_error (_("certificate not yet valid")); log_info ("(valid from "); dump_isotime (not_before); log_printf (")\n"); err = gpg_error (GPG_ERR_CERT_TOO_YOUNG); goto leave; } /* Now check whether the certificate has expired. */ if (*not_after && strcmp (current_time, not_after) > 0 ) { log_error (_("certificate has expired")); log_info ("(expired at "); dump_isotime (not_after); log_printf (")\n"); any_expired = 1; } } /* Do we have any critical extensions in the certificate we can't handle? */ err = unknown_criticals (subject_cert); if (err) goto leave; /* yes. */ /* Check that given policies are allowed. */ err = check_cert_policy (subject_cert); if (gpg_err_code (err) == GPG_ERR_NO_POLICY_MATCH) { any_no_policy_match = 1; err = 0; } else if (err) goto leave; /* Is this a self-signed certificate? */ if (is_root_cert (subject_cert, issuer, subject)) { /* There is no need to check the signature of the trust anchor. */ /* if (check_cert_sig (subject_cert, subject_cert) ) */ /* { */ /* log_error (_("selfsigned certificate has a BAD signature")); */ /* err = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN */ /* : GPG_ERR_BAD_CERT); */ /* goto leave; */ /* } */ /* Is this certificate allowed to act as a CA. */ err = allowed_ca (subject_cert, NULL); if (err) goto leave; /* No. */ err = is_trusted_cert (subject_cert, (flags & VALIDATE_FLAG_MASK_TRUST)); if (!err) ; /* Yes we trust this cert. */ else if (gpg_err_code (err) == GPG_ERR_NOT_TRUSTED) { char *fpr; log_error (_("root certificate is not marked trusted")); fpr = get_fingerprint_hexstring (subject_cert); log_info (_("fingerprint=%s\n"), fpr? fpr : "?"); dump_cert ("issuer", subject_cert); if (r_trust_anchor) { /* Caller wants to do another trustiness check. */ *r_trust_anchor = fpr; err = 0; } else xfree (fpr); } else { log_error (_("checking trustworthiness of " "root certificate failed: %s\n"), gpg_strerror (err)); } if (err) goto leave; /* Prepend the certificate to our list. */ { chain_item_t ci; ci = xtrycalloc (1, sizeof *ci); if (!ci) { err = gpg_error_from_errno (errno); goto leave; } ksba_cert_ref (subject_cert); ci->cert = subject_cert; cert_compute_fpr (subject_cert, ci->fpr); ci->next = chain; chain = ci; } if (opt.verbose) { if (r_trust_anchor && *r_trust_anchor) log_info ("root certificate is good but not trusted\n"); else log_info ("root certificate is good and trusted\n"); } break; /* Okay: a self-signed certificate is an end-point. */ } /* To avoid loops, we use an arbitrary limit on the length of the chain. */ depth++; if (depth > maxdepth) { log_error (_("certificate chain too long\n")); err = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } /* Find the next cert up the tree. */ ksba_cert_release (issuer_cert); issuer_cert = NULL; err = find_issuing_cert (ctrl, subject_cert, &issuer_cert); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { log_error (_("issuer certificate not found")); log_info ("issuer certificate: #/"); dump_string (issuer); log_printf ("\n"); } else log_error (_("issuer certificate not found: %s\n"), gpg_strerror (err)); /* Use a better understandable error code. */ err = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); goto leave; } /* try_another_cert: */ if (DBG_X509) { log_debug ("got issuer's certificate:\n"); dump_cert ("issuer", issuer_cert); } /* Now check the signature of the certificate. FIXME: we should * delay this until later so that faked certificates can't be * turned into a DoS easily. */ err = check_cert_sig (issuer_cert, subject_cert); if (err) { log_error (_("certificate has a BAD signature")); #if 0 if (gpg_err_code (err) == 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 without using a authorityKeyIdentifier. */ rc = find_up (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. */ rc = gpg_error (GPG_ERR_BAD_SIGNATURE); ksba_cert_release (tmp_cert); } else { do_list (0, lm, fp, _("found another possible matching " "CA certificate - trying again")); ksba_cert_release (issuer_cert); issuer_cert = tmp_cert; goto try_another_cert; } } } #endif /* Return a more descriptive error code than the one * returned from the signature checking. */ err = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } /* Check that the length of the chain is not longer than allowed * by the CA. */ { int chainlen; err = allowed_ca (issuer_cert, &chainlen); if (err) goto leave; if (chainlen >= 0 && (depth - 1) > chainlen) { log_error (_("certificate chain longer than allowed by CA (%d)"), chainlen); err = gpg_error (GPG_ERR_BAD_CERT_CHAIN); goto leave; } } /* May that certificate be used for certification? */ err = check_cert_use_cert (issuer_cert); if (err) goto leave; /* No. */ /* Prepend the certificate to our list. */ { chain_item_t ci; ci = xtrycalloc (1, sizeof *ci); if (!ci) { err = gpg_error_from_errno (errno); goto leave; } ksba_cert_ref (subject_cert); ci->cert = subject_cert; cert_compute_fpr (subject_cert, ci->fpr); ci->next = chain; chain = ci; } if (opt.verbose) log_info (_("certificate is good\n")); /* Now to the next level up. */ subject_cert = issuer_cert; issuer_cert = NULL; } /* Even if we have no error here we need to check whether we * encountered an error somewhere during the checks. Set the error * code to the most critical one. */ if (!err) { if (any_expired) err = gpg_error (GPG_ERR_CERT_EXPIRED); else if (any_no_policy_match) err = gpg_error (GPG_ERR_NO_POLICY_MATCH); } if (!err && opt.verbose) { chain_item_t citem; log_info (_("certificate chain is good\n")); for (citem = chain; citem; citem = citem->next) cert_log_name (" certificate", citem->cert); } /* Now check for revocations unless CRL checks are disabled or we * are non-recursive CRL mode. */ if (!err && !(flags & VALIDATE_FLAG_NOCRLCHECK) && !((flags & VALIDATE_FLAG_CRL) && !(flags & VALIDATE_FLAG_RECURSIVE))) { /* Now that everything is fine, walk the chain and check each * certificate for revocations. * * 1. item in the chain - The root certificate. * 2. item - the CA below the root * last item - the target certificate. * * Now for each certificate in the chain check whether it has * been included in a CRL and thus be revoked. We don't do OCSP * here because this does not seem to make much sense. This * might become a recursive process and we should better cache * our validity results to avoid double work. Far worse a * catch-22 may happen for an improper setup hierarchy and we * need a way to break up such a deadlock. */ err = check_revocations (ctrl, chain); } if (!err && opt.verbose) { if (r_trust_anchor && *r_trust_anchor) log_info ("target certificate may be valid\n"); else log_info ("target certificate is valid\n"); } else if (err && opt.verbose) log_info ("target certificate is NOT valid\n"); leave: if (!err && !(r_trust_anchor && *r_trust_anchor)) { /* With no error we can update the validation cache. We do this * for all certificates in the chain. Note that we can't use * the cache if the caller requested to check the trustiness of * the root certificate himself. Adding such a feature would * require us to also store the fingerprint of root * certificate. */ chain_item_t citem; time_t validated_at = gnupg_get_time (); for (citem = chain; citem; citem = citem->next) { err = ksba_cert_set_user_data (citem->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; } } } if (r_exptime) gnupg_copy_time (r_exptime, exptime); ksba_free (issuer); ksba_free (subject); ksba_cert_release (issuer_cert); if (subject_cert != cert) ksba_cert_release (subject_cert); while (chain) { chain_item_t ci_next = chain->next; if (chain->cert) ksba_cert_release (chain->cert); xfree (chain); chain = ci_next; } if (err && r_trust_anchor && *r_trust_anchor) { xfree (*r_trust_anchor); *r_trust_anchor = NULL; } return err; } /* Return the public key algorithm id from the S-expression PKEY. FIXME: libgcrypt should provide such a function. Note that this implementation uses the names as used by libksba. */ int pk_algo_from_sexp (gcry_sexp_t pkey) { gcry_sexp_t l1, l2; const char *name; size_t n; int algo; l1 = gcry_sexp_find_token (pkey, "public-key", 0); if (!l1) return 0; /* Not found. */ l2 = gcry_sexp_cadr (l1); gcry_sexp_release (l1); name = gcry_sexp_nth_data (l2, 0, &n); if (!name) algo = 0; /* Not found. */ else if (n==3 && !memcmp (name, "rsa", 3)) algo = GCRY_PK_RSA; else if (n==3 && !memcmp (name, "dsa", 3)) algo = GCRY_PK_DSA; else if (n==3 && !memcmp (name, "ecc", 3)) algo = GCRY_PK_ECC; else if (n==13 && !memcmp (name, "ambiguous-rsa", 13)) algo = GCRY_PK_RSA; else algo = 0; gcry_sexp_release (l2); return algo; } /* Return the hash algorithm's algo id from its name given in the * non-null termnated string in (buffer,buflen). Returns 0 on failure * or if the algo is not known. */ static int hash_algo_from_buffer (const void *buffer, size_t buflen) { char *string; int algo; string = xtrymalloc (buflen + 1); if (!string) { log_error (_("out of core\n")); return 0; } memcpy (string, buffer, buflen); string[buflen] = 0; algo = gcry_md_map_name (string); if (!algo) log_error ("unknown digest algorithm '%s' used in certificate\n", string); xfree (string); return algo; } /* Return an unsigned integer from the non-null termnated string * (buffer,buflen). Returns 0 on failure. */ static unsigned int uint_from_buffer (const void *buffer, size_t buflen) { char *string; unsigned int val; string = xtrymalloc (buflen + 1); if (!string) { log_error (_("out of core\n")); return 0; } memcpy (string, buffer, buflen); string[buflen] = 0; val = strtoul (string, NULL, 10); xfree (string); return val; } /* Check the signature on CERT using the ISSUER_CERT. This function * does only test the cryptographic signature and nothing else. It is * assumed that the ISSUER_CERT is valid. */ static gpg_error_t check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert) { gpg_error_t err; const char *algoid; gcry_md_hd_t md; int algo; ksba_sexp_t p; size_t n; - gcry_sexp_t s_sig, s_hash, s_pkey; + gcry_sexp_t s_sig, s_pkey; + gcry_sexp_t s_hash = NULL; const char *algo_name; /* hash algorithm name converted to lower case. */ int digestlen; unsigned char *digest; int use_pss = 0; unsigned int saltlen = 0; /* (use is actually controlled by use_pss) */ /* Hash the target certificate using the algorithm from that certificate. */ algoid = ksba_cert_get_digest_algo (cert); algo = gcry_md_map_name (algoid); if (!algo && algoid && !strcmp (algoid, "1.2.840.113549.1.1.10")) use_pss = 1; else if (!algo) { log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?"); return gpg_error (GPG_ERR_GENERAL); } /* Get the signature value out of the target certificate. */ p = ksba_cert_get_sig_val (cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); ksba_free (p); return gpg_error (GPG_ERR_BUG); } err = gcry_sexp_sscan ( &s_sig, NULL, p, n); ksba_free (p); if (err) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err)); return err; } if (DBG_CRYPTO) gcry_log_debugsxp ("sigval", s_sig); if (use_pss) { /* Extract the hash algorithm and the salt length from the sigval. */ gcry_buffer_t ioarray[2] = { {0}, {0} }; err = gcry_sexp_extract_param (s_sig, "sig-val", "&'hash-algo''salt-length'", ioarray+0, ioarray+1, NULL); if (err) { gcry_sexp_release (s_sig); log_error ("extracting params from PSS failed: %s\n", gpg_strerror (err)); return err; } algo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len); saltlen = uint_from_buffer (ioarray[1].data, ioarray[1].len); xfree (ioarray[0].data); xfree (ioarray[1].data); if (saltlen < 20) { log_error ("length of PSS salt too short\n"); gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_DIGEST_ALGO); } if (!algo) { gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_DIGEST_ALGO); } /* Add some restrictions; see ../sm/certcheck.c for details. */ switch (algo) { case GCRY_MD_SHA1: case GCRY_MD_SHA256: case GCRY_MD_SHA384: case GCRY_MD_SHA512: case GCRY_MD_SHA3_256: case GCRY_MD_SHA3_384: case GCRY_MD_SHA3_512: break; default: log_error ("PSS hash algorithm '%s' rejected\n", gcry_md_algo_name (algo)); gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_DIGEST_ALGO); } if (gcry_md_get_algo_dlen (algo) != saltlen) { log_error ("PSS hash algorithm '%s' rejected due to salt length %u\n", gcry_md_algo_name (algo), saltlen); gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_DIGEST_ALGO); } } algo_name = hash_algo_to_string (algo); err = gcry_md_open (&md, algo, 0); if (err) { log_error ("md_open failed: %s\n", gpg_strerror (err)); gcry_sexp_release (s_sig); return err; } if (DBG_HASHING) gcry_md_debug (md, "hash.cert"); err = ksba_cert_hash (cert, 1, HASH_FNC, md); if (err) { log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (err)); gcry_md_close (md); gcry_sexp_release (s_sig); return err; } gcry_md_final (md); /* Get the public key from the issuer certificate. */ p = ksba_cert_get_public_key (issuer_cert); n = gcry_sexp_canon_len (p, 0, NULL, NULL); if (!n) { log_error ("libksba did not return a proper S-Exp\n"); gcry_md_close (md); ksba_free (p); gcry_sexp_release (s_sig); return gpg_error (GPG_ERR_BUG); } err = gcry_sexp_sscan ( &s_pkey, NULL, p, n); ksba_free (p); if (err) { log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err)); gcry_md_close (md); gcry_sexp_release (s_sig); return err; } /* Prepare the values for signature verification. At this point we * have these values: * * S_PKEY - S-expression with the issuer's public key. * S_SIG - Signature value as given in the certificate. * MD - Finalized hash context with hash of the certificate. * ALGO_NAME - Lowercase hash algorithm name * SALTLEN - Salt length for rsaPSS. */ digestlen = gcry_md_get_algo_dlen (algo); digest = gcry_md_read (md, algo); if (use_pss) { err = gcry_sexp_build (&s_hash, NULL, "(data (flags pss)" "(hash %s %b)" "(salt-length %u))", algo_name, (int)digestlen, digest, saltlen); } else 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 (digestlen < qbits/8) { log_error (_("a %u bit hash is not valid for a %u bit %s key\n"), (unsigned int)digestlen*8, qbits0, "ECDSA"); if (digestlen < 20) { err = gpg_error (GPG_ERR_INTERNAL); goto leave; } } /* Truncate. */ if (digestlen > qbits/8) digestlen = qbits/8; err = gcry_sexp_build (&s_hash, NULL, "(data(flags raw)(value %b))", (int)digestlen, digest); } else /* Not DSA - we assume RSA */ { err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))", algo_name, (int)digestlen, digest); } if (!err) err = gcry_pk_verify (s_sig, s_hash, s_pkey); if (DBG_X509) log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err)); leave: gcry_md_close (md); gcry_sexp_release (s_sig); gcry_sexp_release (s_hash); gcry_sexp_release (s_pkey); return err; } /* Return 0 if CERT is usable for MODE. */ static gpg_error_t check_cert_usage (ksba_cert_t cert, enum cert_usage_modes mode) { gpg_error_t err; unsigned int use; char *extkeyusages; int have_ocsp_signing = 0; err = ksba_cert_get_ext_key_usages (cert, &extkeyusages); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; /* No policy given. */ if (!err) { unsigned int extusemask = ~0; /* Allow all. */ if (extkeyusages) { char *p, *pend; int any_critical = 0; extusemask = 0; p = extkeyusages; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; /* Only care about critical flagged usages. */ if ( *pend == 'C' ) { any_critical = 1; if ( !strcmp (p, oid_kp_serverAuth)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_clientAuth)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_codeSigning)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE); else if ( !strcmp (p, oid_kp_emailProtection)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION | KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_KEY_AGREEMENT); else if ( !strcmp (p, oid_kp_timeStamping)) extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION); } /* This is a hack to cope with OCSP. Note that we do not yet fully comply with the requirements and that the entire CRL/OCSP checking thing should undergo a thorough review and probably redesign. */ if ( !strcmp (p, oid_kp_ocspSigning)) have_ocsp_signing = 1; if ((p = strchr (pend, '\n'))) p++; } ksba_free (extkeyusages); extkeyusages = NULL; if (!any_critical) extusemask = ~0; /* Reset to the don't care mask. */ } err = ksba_cert_get_key_usage (cert, &use); if (gpg_err_code (err) == GPG_ERR_NO_DATA) { err = 0; if (opt.verbose && (mode == CERT_USAGE_MODE_SIGN || mode == CERT_USAGE_MODE_ENCR)) log_info (_("no key usage specified - assuming all usages\n")); use = ~0; } /* Apply extKeyUsage. */ use &= extusemask; } if (err) { log_error (_("error getting key usage information: %s\n"), gpg_strerror (err)); ksba_free (extkeyusages); return err; } switch (mode) { case CERT_USAGE_MODE_SIGN: case CERT_USAGE_MODE_VRFY: if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE | KSBA_KEYUSAGE_NON_REPUDIATION))) return 0; log_info (mode == CERT_USAGE_MODE_VRFY ? _("certificate should not have been used for signing\n") : _("certificate is not usable for signing\n")); break; case CERT_USAGE_MODE_ENCR: case CERT_USAGE_MODE_DECR: if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT | KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) return 0; log_info (mode == CERT_USAGE_MODE_DECR ? _("certificate should not have been used for encryption\n") : _("certificate is not usable for encryption\n")); break; case CERT_USAGE_MODE_CERT: if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN))) return 0; log_info (_("certificate should not have " "been used for certification\n")); break; case CERT_USAGE_MODE_OCSP: if (use != ~0 && (have_ocsp_signing || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN |KSBA_KEYUSAGE_CRL_SIGN)))) return 0; log_info (_("certificate should not have " "been used for OCSP response signing\n")); break; case CERT_USAGE_MODE_CRL: if ((use & (KSBA_KEYUSAGE_CRL_SIGN))) return 0; log_info (_("certificate should not have " "been used for CRL signing\n")); break; } return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } /* Return 0 if the certificate CERT is usable for certification. */ gpg_error_t check_cert_use_cert (ksba_cert_t cert) { return check_cert_usage (cert, CERT_USAGE_MODE_CERT); } /* Return 0 if the certificate CERT is usable for signing OCSP responses. */ gpg_error_t check_cert_use_ocsp (ksba_cert_t cert) { return check_cert_usage (cert, CERT_USAGE_MODE_OCSP); } /* Return 0 if the certificate CERT is usable for signing CRLs. */ gpg_error_t check_cert_use_crl (ksba_cert_t cert) { return check_cert_usage (cert, CERT_USAGE_MODE_CRL); } diff --git a/g10/key-check.c b/g10/key-check.c index b370cc11b..a2d8029cc 100644 --- a/g10/key-check.c +++ b/g10/key-check.c @@ -1,760 +1,761 @@ /* key-check.c - Detect and fix various problems with keys * Copyright (C) 1998-2010 Free Software Foundation, Inc. * Copyright (C) 1998-2017 Werner Koch * Copyright (C) 2015-2018 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 . */ #include #include "gpg.h" #include "options.h" #include "packet.h" #include "keydb.h" #include "main.h" #include "../common/ttyio.h" #include "../common/i18n.h" #include "keyedit.h" #include "key-check.h" /* Print PREFIX followed by TEXT. With mode > 0 use log_info, with * mode < 0 use ttyio, else print to stdout. If TEXT is not NULL, it * may be modified by this function. */ static void print_info (int mode, const char *prefix, char *text) { char *p; if (!text) text = ""; else if ((p = strchr (text,'\n'))) *p = 0; /* Strip LF. */ if (mode > 0) log_info ("%s %s\n", prefix, text); else tty_fprintf (mode? NULL:es_stdout, "%s %s\n", prefix, text); } /* Order two signatures. The actual ordering isn't important. Our * goal is to ensure that identical signatures occur together. */ static int sig_comparison (const void *av, const void *bv) { const KBNODE an = *(const KBNODE *) av; const KBNODE bn = *(const KBNODE *) bv; const PKT_signature *a; const PKT_signature *b; int ndataa; int ndatab; int i; log_assert (an->pkt->pkttype == PKT_SIGNATURE); log_assert (bn->pkt->pkttype == PKT_SIGNATURE); a = an->pkt->pkt.signature; b = bn->pkt->pkt.signature; /* Signatures with a different help counter are not identical for * our purpose. */ if (a->help_counter < b->help_counter) return -1; if (a->help_counter > b->help_counter) return 1; if (a->digest_algo < b->digest_algo) return -1; if (a->digest_algo > b->digest_algo) return 1; ndataa = pubkey_get_nsig (a->pubkey_algo); ndatab = pubkey_get_nsig (b->pubkey_algo); if (ndataa != ndatab) return (ndataa < ndatab)? -1 : 1; for (i = 0; i < ndataa; i ++) { int c = gcry_mpi_cmp (a->data[i], b->data[i]); if (c != 0) return c; } /* Okay, they are equal. */ return 0; } static gpg_error_t remove_duplicate_sigs (kbnode_t kb, int *dups, int *modified) { gpg_error_t err; kbnode_t n; int nsigs; kbnode_t *sigs; /* Allocated array with the signature packet. */ int i; int last_i; int block; PKT_signature *sig; /* Count the sigs. */ for (nsigs = 0, n = kb; n; n = n->next) { if (is_deleted_kbnode (n)) continue; else if (n->pkt->pkttype == PKT_SIGNATURE) nsigs ++; } if (!nsigs) return 0; /* No signatures at all. */ /* Add them all to the SIGS array. */ sigs = xtrycalloc (nsigs, sizeof *sigs); if (!sigs) { err = gpg_error_from_syserror (); log_error (_("error allocating memory: %s\n"), gpg_strerror (err)); return err; } block = 0; i = 0; for (n = kb; n; n = n->next) { if (is_deleted_kbnode (n)) continue; if (n->pkt->pkttype != PKT_SIGNATURE) { switch (n->pkt->pkttype) { case PKT_PUBLIC_SUBKEY: case PKT_SECRET_SUBKEY: case PKT_USER_ID: case PKT_ATTRIBUTE: /* Bump the block number so that we only consider * signatures below the same object as duplicates. */ block++; break; default: break; } continue; } sig = n->pkt->pkt.signature; sig->help_counter = block; sigs[i++] = n; } log_assert (i == nsigs); qsort (sigs, nsigs, sizeof (sigs[0]), sig_comparison); last_i = 0; for (i = 1; i < nsigs; i ++) { log_assert (sigs[last_i]); log_assert (sigs[last_i]->pkt->pkttype == PKT_SIGNATURE); log_assert (sigs[i]); log_assert (sigs[i]->pkt->pkttype == PKT_SIGNATURE); if (sig_comparison (&sigs[last_i], &sigs[i]) == 0) { /* They are the same. Kill the latter. */ if (DBG_PACKET) { sig = sigs[i]->pkt->pkt.signature; log_debug ("Signature appears multiple times, " "deleting duplicate:\n"); log_debug (" sig: class 0x%x, issuer: %s," " timestamp: %s (%lld), digest: %02x %02x\n", sig->sig_class, keystr (sig->keyid), isotimestamp (sig->timestamp), (long long) sig->timestamp, sig->digest_start[0], sig->digest_start[1]); } /* Remove sigs[i] from the keyblock. */ { kbnode_t z, *prevp; int to_kill = last_i; last_i = i; for (prevp = &kb, z = kb; z; prevp = &z->next, z = z->next) if (z == sigs[to_kill]) break; *prevp = sigs[to_kill]->next; sigs[to_kill]->next = NULL; release_kbnode (sigs[to_kill]); sigs[to_kill] = NULL; ++*dups; *modified = 1; } } else last_i = i; } xfree (sigs); return 0; } /* Perform a few sanity checks on a keyblock is okay and possibly * repair some damage. Concretely: * * - Detect duplicate signatures and remove them. * * - Detect out of order signatures and relocate them (e.g., a sig * over user id X located under subkey Y). * * Note: this function does not remove signatures that don't belong or * components that are not signed! (Although it would be trivial to * do so.) * * If ONLY_SELFSIGS is true, then this function only reorders self * signatures (it still checks all signatures for duplicates, * however). * * Allowed values for MODE are: * -1 - print to the TTY * 0 - print to stdout * 1 - use log_info. * * Returns true if the keyblock was modified. */ int key_check_all_keysigs (ctrl_t ctrl, int mode, kbnode_t kb, int only_selected, int only_selfsigs) { gpg_error_t err; PKT_public_key *pk; KBNODE n, n_next, *n_prevp, n2; char *pending_desc = NULL; PKT_public_key *issuer; KBNODE last_printed_component; KBNODE current_component = NULL; int dups = 0; int missing_issuer = 0; int reordered = 0; int bad_signature = 0; int missing_selfsig = 0; int modified = 0; PKT_signature *sig; + (void)missing_selfsig; log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY); pk = kb->pkt->pkt.public_key; /* First we look for duplicates. */ if (remove_duplicate_sigs (kb, &dups, &modified)) goto leave; /* Error */ /* Now make sure the sigs occur after the component (aka block) * (public key, subkey, user id) that they sign. */ issuer = NULL; last_printed_component = NULL; for (n_prevp = &kb, n = kb; n; /* If we moved n, then n_prevp is need valid. */ n_prevp = (n->next == n_next ? &n->next : n_prevp), n = n_next) { PACKET *p; int processed_current_component; int rc; int dump_sig_params = 0; n_next = n->next; if (is_deleted_kbnode (n)) continue; p = n->pkt; if (issuer && issuer != pk) { free_public_key (issuer); issuer = NULL; } xfree (pending_desc); pending_desc = NULL; switch (p->pkttype) { case PKT_PUBLIC_KEY: log_assert (p->pkt.public_key == pk); if (only_selected && ! (n->flag & NODFLG_SELKEY)) { current_component = NULL; break; } if (DBG_PACKET) log_debug ("public key %s: timestamp: %s (%lld)\n", pk_keyid_str (pk), isotimestamp (pk->timestamp), (long long) pk->timestamp); current_component = n; break; case PKT_PUBLIC_SUBKEY: if (only_selected && ! (n->flag & NODFLG_SELKEY)) { current_component = NULL; break; } if (DBG_PACKET) log_debug ("subkey %s: timestamp: %s (%lld)\n", pk_keyid_str (p->pkt.public_key), isotimestamp (p->pkt.public_key->timestamp), (long long) p->pkt.public_key->timestamp); current_component = n; break; case PKT_USER_ID: if (only_selected && ! (n->flag & NODFLG_SELUID)) { current_component = NULL; break; } if (DBG_PACKET) log_debug ("user id: %s\n", p->pkt.user_id->attrib_data ? "[ photo id ]" : p->pkt.user_id->name); current_component = n; break; case PKT_SIGNATURE: if (! current_component) /* The current component is not selected, don't check the sigs under it. */ break; sig = n->pkt->pkt.signature; pending_desc = xasprintf (" sig: class: 0x%x, issuer: %s," " timestamp: %s (%lld), digest: %02x %02x", sig->sig_class, keystr (sig->keyid), isotimestamp (sig->timestamp), (long long) sig->timestamp, sig->digest_start[0], sig->digest_start[1]); if (keyid_cmp (pk_keyid (pk), sig->keyid) == 0) issuer = pk; else /* Issuer is a different key. */ { if (only_selfsigs) continue; issuer = xtrycalloc (1, sizeof *issuer); if (!issuer) err = gpg_error_from_syserror (); else err = get_pubkey (ctrl, issuer, sig->keyid); if (err) { xfree (issuer); issuer = NULL; if (DBG_PACKET) { if (pending_desc) log_debug ("%s", pending_desc); log_debug (" Can't check signature allegedly" " issued by %s: %s\n", keystr (sig->keyid), gpg_strerror (err)); } missing_issuer ++; break; } } if ((err = openpgp_pk_test_algo (sig->pubkey_algo))) { if (DBG_PACKET && pending_desc) log_debug ("%s", pending_desc); log_info (_("can't check signature with unsupported" " public-key algorithm (%d): %s.\n"), sig->pubkey_algo, gpg_strerror (err)); break; } if ((err = openpgp_md_test_algo (sig->digest_algo))) { if (DBG_PACKET && pending_desc) log_debug ("%s", pending_desc); log_info (_("can't check signature with unsupported" " message-digest algorithm %d: %s.\n"), sig->digest_algo, gpg_strerror (err)); break; } /* We iterate over the keyblock. Most likely, the matching component is the current component so always try that first. */ processed_current_component = 0; for (n2 = current_component; n2; n2 = (processed_current_component ? n2->next : kb), processed_current_component = 1) if (is_deleted_kbnode (n2)) continue; else if (processed_current_component && n2 == current_component) /* Don't process it twice. */ continue; else { err = check_signature_over_key_or_uid (ctrl, issuer, sig, kb, n2->pkt, NULL, NULL); if (! err) break; } /* n/sig is a signature and n2 is the component (public key, subkey or user id) that it signs, if any. current_component is that component that it appears to apply to (according to the ordering). */ if (current_component == n2) { if (DBG_PACKET) { log_debug ("%s", pending_desc); log_debug (" Good signature over last key or uid!\n"); } rc = 0; } else if (n2) { log_assert (n2->pkt->pkttype == PKT_USER_ID || n2->pkt->pkttype == PKT_PUBLIC_KEY || n2->pkt->pkttype == PKT_PUBLIC_SUBKEY); if (DBG_PACKET) { log_debug ("%s", pending_desc); log_debug (" Good signature out of order!" " (Over %s (%d) '%s')\n", n2->pkt->pkttype == PKT_USER_ID ? "user id" : n2->pkt->pkttype == PKT_PUBLIC_SUBKEY ? "subkey" : "primary key", n2->pkt->pkttype, n2->pkt->pkttype == PKT_USER_ID ? n2->pkt->pkt.user_id->name : pk_keyid_str (n2->pkt->pkt.public_key)); } /* Reorder the packets: move the signature n to be just after n2. */ /* Unlink the signature. */ log_assert (n_prevp); *n_prevp = n->next; /* Insert the sig immediately after the component. */ n->next = n2->next; n2->next = n; reordered ++; modified = 1; rc = 0; } else { if (DBG_PACKET) { log_debug ("%s", pending_desc); log_debug (" Bad signature.\n"); } if (DBG_PACKET) dump_sig_params = 1; bad_signature ++; rc = GPG_ERR_BAD_SIGNATURE; } /* We don't cache the result here, because we haven't completely checked that the signature is legitimate. For instance, if we have a revocation certificate on Alice's key signed by Bob, the signature may be good, but we haven't checked that Bob is a designated revoker. */ /* cache_sig_result (sig, rc); */ { int has_selfsig = 0; if (! rc && issuer == pk) { if (n2->pkt->pkttype == PKT_PUBLIC_KEY && (/* Direct key signature. */ sig->sig_class == 0x1f /* Key revocation signature. */ || sig->sig_class == 0x20)) has_selfsig = 1; if (n2->pkt->pkttype == PKT_PUBLIC_SUBKEY && (/* Subkey binding sig. */ sig->sig_class == 0x18 /* Subkey revocation sig. */ || sig->sig_class == 0x28)) has_selfsig = 1; if (n2->pkt->pkttype == PKT_USER_ID && (/* Certification sigs. */ sig->sig_class == 0x10 || sig->sig_class == 0x11 || sig->sig_class == 0x12 || sig->sig_class == 0x13 /* Certification revocation sig. */ || sig->sig_class == 0x30)) has_selfsig = 1; } if (DBG_PACKET && ((n2 && n2 != last_printed_component) || (! n2 && last_printed_component != current_component))) { int is_reordered = n2 && n2 != current_component; if (n2) last_printed_component = n2; else last_printed_component = current_component; if (!modified) ; else if (last_printed_component->pkt->pkttype == PKT_USER_ID) { log_debug ("uid "); print_utf8_buffer (log_get_stream (), last_printed_component ->pkt->pkt.user_id->name, last_printed_component ->pkt->pkt.user_id->len); log_flush (); } else if (last_printed_component->pkt->pkttype == PKT_PUBLIC_KEY) log_debug ("pub %s\n", pk_keyid_str (last_printed_component ->pkt->pkt.public_key)); else log_debug ("sub %s\n", pk_keyid_str (last_printed_component ->pkt->pkt.public_key)); if (modified) { if (is_reordered) log_debug ("%s\n", _(" (reordered signatures follow)")); } } if (DBG_PACKET && modified) keyedit_print_one_sig (ctrl, log_get_stream (), rc, kb, n, NULL, NULL, NULL, has_selfsig, 0, only_selfsigs); } if (dump_sig_params) { int i; for (i = 0; i < pubkey_get_nsig (sig->pubkey_algo); i ++) { char buffer[1024]; size_t len; char *printable; if (gcry_mpi_get_flag (sig->data[i], GCRYMPI_FLAG_OPAQUE)) { const byte *sigdata; unsigned int nbits; sigdata = gcry_mpi_get_opaque (sig->data[i], &nbits); len = (nbits+7)/8; memcpy (buffer, sigdata, len); } else gcry_mpi_print (GCRYMPI_FMT_USG, buffer, sizeof (buffer), &len, sig->data[i]); printable = bin2hex (buffer, len, NULL); log_debug (" %d: %s\n", i, printable); xfree (printable); } } break; default: if (DBG_PACKET) log_debug ("unhandled packet: %d\n", p->pkttype); break; } } xfree (pending_desc); pending_desc = NULL; if (issuer != pk) free_public_key (issuer); issuer = NULL; /* If we reordered signatures we need to de-duplicate again because * a signature can now be a duplicate in another block. */ if (reordered) { if (remove_duplicate_sigs (kb, &dups, &modified)) goto leave; } /* Identify keys / uids that don't have a self-sig. */ { int has_selfsig = 0; PACKET *p; current_component = NULL; for (n = kb; n; n = n->next) { if (is_deleted_kbnode (n)) continue; p = n->pkt; switch (p->pkttype) { case PKT_PUBLIC_KEY: case PKT_PUBLIC_SUBKEY: case PKT_USER_ID: if (current_component && ! has_selfsig) missing_selfsig ++; current_component = n; has_selfsig = 0; break; case PKT_SIGNATURE: if (! current_component || has_selfsig) break; sig = n->pkt->pkt.signature; if (! (sig->flags.checked && sig->flags.valid)) break; if (keyid_cmp (pk_keyid (pk), sig->keyid) != 0) /* Different issuer, couldn't be a self-sig. */ break; if (current_component->pkt->pkttype == PKT_PUBLIC_KEY && (/* Direct key signature. */ sig->sig_class == 0x1f /* Key revocation signature. */ || sig->sig_class == 0x20)) has_selfsig = 1; if (current_component->pkt->pkttype == PKT_PUBLIC_SUBKEY && (/* Subkey binding sig. */ sig->sig_class == 0x18 /* Subkey revocation sig. */ || sig->sig_class == 0x28)) has_selfsig = 1; if (current_component->pkt->pkttype == PKT_USER_ID && (/* Certification sigs. */ sig->sig_class == 0x10 || sig->sig_class == 0x11 || sig->sig_class == 0x12 || sig->sig_class == 0x13 /* Certification revocation sig. */ || sig->sig_class == 0x30)) has_selfsig = 1; break; default: if (current_component && ! has_selfsig) missing_selfsig ++; current_component = NULL; } } } leave: if (!opt.quiet) { char prefix[100]; char *p; /* To avoid string changes in 2.2 we strip the LF here. */ snprintf (prefix, sizeof prefix, _("key %s:\n"), pk_keyid_str (pk)); p = strrchr (prefix, '\n'); if (p) *p = 0; if (dups) { p = xtryasprintf (ngettext ("%d duplicate signature removed\n", "%d duplicate signatures removed\n", dups), dups); print_info (mode, prefix, p); xfree (p); } if (missing_issuer) { p = xtryasprintf (ngettext ("%d signature not checked due to a missing key\n", "%d signatures not checked due to missing keys\n", missing_issuer), missing_issuer); print_info (mode, prefix, p); xfree (p); } if (bad_signature) { p = xtryasprintf (ngettext ("%d bad signature\n", "%d bad signatures\n", bad_signature), bad_signature); print_info (mode, prefix, p); xfree (p); } if (reordered) { p = xtryasprintf (ngettext ("%d signature reordered\n", "%d signatures reordered\n", reordered), reordered); print_info (mode, prefix, p); xfree (p); } if (only_selfsigs && (bad_signature || reordered)) { p = xtryasprintf (_("Warning: errors found and only checked self-signatures," " run '%s' to check all signatures.\n"), "check"); print_info (mode, prefix, p); xfree (p); } } return modified; } diff --git a/g10/keyedit.c b/g10/keyedit.c index 2f7263ec6..04538011f 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -1,6631 +1,6633 @@ /* keyedit.c - Edit properties of a key * Copyright (C) 1998-2010 Free Software Foundation, Inc. * Copyright (C) 1998-2017 Werner Koch * Copyright (C) 2015, 2016 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 . */ #include #include #include #include #include #include #ifdef HAVE_LIBREADLINE # define GNUPG_LIBREADLINE_H_INCLUDED # include #endif #include "gpg.h" #include "options.h" #include "packet.h" #include "../common/status.h" #include "../common/iobuf.h" #include "keydb.h" #include "photoid.h" #include "../common/util.h" #include "main.h" #include "trustdb.h" #include "filter.h" #include "../common/ttyio.h" #include "../common/status.h" #include "../common/i18n.h" #include "keyserver-internal.h" #include "call-agent.h" #include "../common/host2net.h" #include "tofu.h" #include "key-check.h" #include "key-clean.h" #include "keyedit.h" static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig, int verbose); static void show_names (ctrl_t ctrl, estream_t fp, kbnode_t keyblock, PKT_public_key * pk, unsigned int flag, int with_prefs); static void show_key_with_all_names (ctrl_t ctrl, estream_t fp, KBNODE keyblock, int only_marked, int with_revoker, int with_fpr, int with_subkeys, int with_prefs, int nowarn); static void show_key_and_fingerprint (ctrl_t ctrl, kbnode_t keyblock, int with_subkeys); static void show_key_and_grip (kbnode_t keyblock); static void subkey_expire_warning (kbnode_t keyblock); static int menu_adduid (ctrl_t ctrl, kbnode_t keyblock, int photo, const char *photo_name, const char *uidstr); static void menu_deluid (KBNODE pub_keyblock); static int menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only); static void menu_delkey (KBNODE pub_keyblock); static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive); static gpg_error_t menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock, int unattended, u32 newexpiration); static int menu_changeusage (ctrl_t ctrl, kbnode_t keyblock); static int menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_set_keyserver_url (ctrl_t ctrl, const char *url, kbnode_t pub_keyblock); static int menu_set_notation (ctrl_t ctrl, const char *string, kbnode_t pub_keyblock); static int menu_select_uid (KBNODE keyblock, int idx); static int menu_select_uid_namehash (KBNODE keyblock, const char *namehash); static int menu_select_key (KBNODE keyblock, int idx, char *p); static int count_uids (KBNODE keyblock); static int count_uids_with_flag (KBNODE keyblock, unsigned flag); static int count_keys_with_flag (KBNODE keyblock, unsigned flag); static int count_selected_uids (KBNODE keyblock); static int real_uids_left (KBNODE keyblock); static int count_selected_keys (KBNODE keyblock); static int menu_revsig (ctrl_t ctrl, kbnode_t keyblock); static int menu_revuid (ctrl_t ctrl, kbnode_t keyblock); static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node, const struct revocation_reason_info *reason, int *modified); static int menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock); static int menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock); #ifndef NO_TRUST_MODELS static int enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable); #endif /*!NO_TRUST_MODELS*/ static void menu_showphoto (ctrl_t ctrl, kbnode_t keyblock); static int update_trust = 0; #define CONTROL_D ('D' - 'A' + 1) struct sign_attrib { int non_exportable, non_revocable; struct revocation_reason_info *reason; byte trust_depth, trust_value; char *trust_regexp; }; /* TODO: Fix duplicated code between here and the check-sigs/list-sigs code in keylist.c. */ static int print_and_check_one_sig_colon (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node, int *inv_sigs, int *no_key, int *oth_err, int *is_selfsig, int print_without_key) { PKT_signature *sig = node->pkt->pkt.signature; int rc, sigrc; /* TODO: Make sure a cached sig record here still has the pk that issued it. See also keylist.c:list_keyblock_print */ rc = check_key_signature (ctrl, keyblock, node, is_selfsig); switch (gpg_err_code (rc)) { case 0: node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR); sigrc = '!'; break; case GPG_ERR_BAD_SIGNATURE: node->flag = NODFLG_BADSIG; sigrc = '-'; if (inv_sigs) ++ * inv_sigs; break; case GPG_ERR_NO_PUBKEY: case GPG_ERR_UNUSABLE_PUBKEY: node->flag = NODFLG_NOKEY; sigrc = '?'; if (no_key) ++ * no_key; break; default: node->flag = NODFLG_SIGERR; sigrc = '%'; if (oth_err) ++ * oth_err; break; } if (sigrc != '?' || print_without_key) { es_printf ("sig:%c::%d:%08lX%08lX:%lu:%lu:", sigrc, sig->pubkey_algo, (ulong) sig->keyid[0], (ulong) sig->keyid[1], (ulong) sig->timestamp, (ulong) sig->expiredate); if (sig->trust_depth || sig->trust_value) es_printf ("%d %d", sig->trust_depth, sig->trust_value); es_printf (":"); if (sig->trust_regexp) es_write_sanitized (es_stdout, sig->trust_regexp, strlen (sig->trust_regexp), ":", NULL); es_printf ("::%02x%c\n", sig->sig_class, sig->flags.exportable ? 'x' : 'l'); if (opt.show_subpackets) print_subpackets_colon (sig); } return (sigrc == '!'); } /* * Print information about a signature (rc is its status), check it * and return true if the signature is okay. NODE must be a signature * packet. With EXTENDED set all possible signature list options will * always be printed. */ int keyedit_print_one_sig (ctrl_t ctrl, estream_t fp, int rc, kbnode_t keyblock, kbnode_t node, int *inv_sigs, int *no_key, int *oth_err, int is_selfsig, int print_without_key, int extended) { PKT_signature *sig = node->pkt->pkt.signature; int sigrc; int is_rev = sig->sig_class == 0x30; /* TODO: Make sure a cached sig record here still has the pk that issued it. See also keylist.c:list_keyblock_print */ switch (gpg_err_code (rc)) { case 0: node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR); sigrc = '!'; break; case GPG_ERR_BAD_SIGNATURE: node->flag = NODFLG_BADSIG; sigrc = '-'; if (inv_sigs) ++ * inv_sigs; break; case GPG_ERR_NO_PUBKEY: case GPG_ERR_UNUSABLE_PUBKEY: node->flag = NODFLG_NOKEY; sigrc = '?'; if (no_key) ++ * no_key; break; default: node->flag = NODFLG_SIGERR; sigrc = '%'; if (oth_err) ++ * oth_err; break; } if (sigrc != '?' || print_without_key) { tty_fprintf (fp, "%s%c%c %c%c%c%c%c%c %s %s", is_rev ? "rev" : "sig", sigrc, (sig->sig_class - 0x10 > 0 && sig->sig_class - 0x10 < 4) ? '0' + sig->sig_class - 0x10 : ' ', sig->flags.exportable ? ' ' : 'L', sig->flags.revocable ? ' ' : 'R', sig->flags.policy_url ? 'P' : ' ', sig->flags.notation ? 'N' : ' ', sig->flags.expired ? 'X' : ' ', (sig->trust_depth > 9) ? 'T' : (sig->trust_depth > 0) ? '0' + sig->trust_depth : ' ', keystr (sig->keyid), datestr_from_sig (sig)); if ((opt.list_options & LIST_SHOW_SIG_EXPIRE) || extended ) tty_fprintf (fp, " %s", expirestr_from_sig (sig)); tty_fprintf (fp, " "); if (sigrc == '%') tty_fprintf (fp, "[%s] ", gpg_strerror (rc)); else if (sigrc == '?') ; else if (is_selfsig) { tty_fprintf (fp, is_rev ? _("[revocation]") : _("[self-signature]")); if (extended && sig->flags.chosen_selfsig) tty_fprintf (fp, "*"); } else { size_t n; char *p = get_user_id (ctrl, sig->keyid, &n, NULL); tty_print_utf8_string2 (fp, p, n, opt.screen_columns - keystrlen () - 26 - ((opt. list_options & LIST_SHOW_SIG_EXPIRE) ? 11 : 0)); xfree (p); } if (fp == log_get_stream ()) log_printf ("\n"); else tty_fprintf (fp, "\n"); if (sig->flags.policy_url && ((opt.list_options & LIST_SHOW_POLICY_URLS) || extended)) show_policy_url (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0)); if (sig->flags.notation && ((opt.list_options & LIST_SHOW_NOTATIONS) || extended)) show_notation (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0), ((opt. list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0) + ((opt. list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0)); if (sig->flags.pref_ks && ((opt.list_options & LIST_SHOW_KEYSERVER_URLS) || extended)) show_keyserver_url (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0)); if (extended) { PKT_public_key *pk = keyblock->pkt->pkt.public_key; const unsigned char *s; s = parse_sig_subpkt (sig, 1, SIGSUBPKT_PRIMARY_UID, NULL); if (s && *s) tty_fprintf (fp, " [primary]\n"); s = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_EXPIRE, NULL); if (s && buf32_to_u32 (s)) tty_fprintf (fp, " [expires: %s]\n", isotimestamp (pk->timestamp + buf32_to_u32 (s))); } } return (sigrc == '!'); } static int print_and_check_one_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node, int *inv_sigs, int *no_key, int *oth_err, int *is_selfsig, int print_without_key, int extended) { int rc; rc = check_key_signature (ctrl, keyblock, node, is_selfsig); return keyedit_print_one_sig (ctrl, NULL, rc, keyblock, node, inv_sigs, no_key, oth_err, *is_selfsig, print_without_key, extended); } static int sign_mk_attrib (PKT_signature * sig, void *opaque) { struct sign_attrib *attrib = opaque; byte buf[8]; if (attrib->non_exportable) { buf[0] = 0; /* not exportable */ build_sig_subpkt (sig, SIGSUBPKT_EXPORTABLE, buf, 1); } if (attrib->non_revocable) { buf[0] = 0; /* not revocable */ build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1); } if (attrib->reason) revocation_reason_build_cb (sig, attrib->reason); if (attrib->trust_depth) { /* Not critical. If someone doesn't understand trust sigs, this can still be a valid regular signature. */ buf[0] = attrib->trust_depth; buf[1] = attrib->trust_value; build_sig_subpkt (sig, SIGSUBPKT_TRUST, buf, 2); /* Critical. If someone doesn't understands regexps, this whole sig should be invalid. Note the +1 for the length - regexps are null terminated. */ if (attrib->trust_regexp) build_sig_subpkt (sig, SIGSUBPKT_FLAG_CRITICAL | SIGSUBPKT_REGEXP, attrib->trust_regexp, strlen (attrib->trust_regexp) + 1); } return 0; } static void trustsig_prompt (byte * trust_value, byte * trust_depth, char **regexp) { char *p; *trust_value = 0; *trust_depth = 0; *regexp = NULL; /* Same string as pkclist.c:do_edit_ownertrust */ tty_printf (_ ("Please decide how far you trust this user to correctly verify" " other users' keys\n(by looking at passports, checking" " fingerprints from different sources, etc.)\n")); tty_printf ("\n"); tty_printf (_(" %d = I trust marginally\n"), 1); tty_printf (_(" %d = I trust fully\n"), 2); tty_printf ("\n"); while (*trust_value == 0) { p = cpr_get ("trustsig_prompt.trust_value", _("Your selection? ")); trim_spaces (p); cpr_kill_prompt (); /* 60 and 120 are as per RFC2440 */ if (p[0] == '1' && !p[1]) *trust_value = 60; else if (p[0] == '2' && !p[1]) *trust_value = 120; xfree (p); } tty_printf ("\n"); tty_printf (_("Please enter the depth of this trust signature.\n" "A depth greater than 1 allows the key you are" " signing to make\n" "trust signatures on your behalf.\n")); tty_printf ("\n"); while (*trust_depth == 0) { p = cpr_get ("trustsig_prompt.trust_depth", _("Your selection? ")); trim_spaces (p); cpr_kill_prompt (); *trust_depth = atoi (p); xfree (p); } tty_printf ("\n"); tty_printf (_("Please enter a domain to restrict this signature, " "or enter for none.\n")); tty_printf ("\n"); p = cpr_get ("trustsig_prompt.trust_regexp", _("Your selection? ")); trim_spaces (p); cpr_kill_prompt (); if (strlen (p) > 0) { char *q = p; int regexplen = 100, ind; *regexp = xmalloc (regexplen); /* Now mangle the domain the user entered into a regexp. To do this, \-escape everything that isn't alphanumeric, and attach "<[^>]+[@.]" to the front, and ">$" to the end. */ strcpy (*regexp, "<[^>]+[@.]"); ind = strlen (*regexp); while (*q) { if (!((*q >= 'A' && *q <= 'Z') || (*q >= 'a' && *q <= 'z') || (*q >= '0' && *q <= '9'))) (*regexp)[ind++] = '\\'; (*regexp)[ind++] = *q; if ((regexplen - ind) < 3) { regexplen += 100; *regexp = xrealloc (*regexp, regexplen); } q++; } (*regexp)[ind] = '\0'; strcat (*regexp, ">$"); } xfree (p); tty_printf ("\n"); } /* * Loop over all LOCUSR and sign the uids after asking. If no * user id is marked, all user ids will be signed; if some user_ids * are marked only those will be signed. If QUICK is true the * function won't ask the user and use sensible defaults. */ static int sign_uids (ctrl_t ctrl, estream_t fp, kbnode_t keyblock, strlist_t locusr, int *ret_modified, int local, int nonrevocable, int trust, int interactive, int quick) { int rc = 0; SK_LIST sk_list = NULL; SK_LIST sk_rover = NULL; PKT_public_key *pk = NULL; KBNODE node, uidnode; PKT_public_key *primary_pk = NULL; int select_all = !count_selected_uids (keyblock) || interactive; /* Build a list of all signators. * * We use the CERT flag to request the primary which must always * be one which is capable of signing keys. I can't see a reason * why to sign keys using a subkey. Implementation of USAGE_CERT * is just a hack in getkey.c and does not mean that a subkey * marked as certification capable will be used. */ rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_CERT); if (rc) goto leave; /* Loop over all signators. */ for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next) { u32 sk_keyid[2], pk_keyid[2]; char *p, *trust_regexp = NULL; int class = 0, selfsig = 0; u32 duration = 0, timestamp = 0; byte trust_depth = 0, trust_value = 0; pk = sk_rover->pk; keyid_from_pk (pk, sk_keyid); /* Set mark A for all selected user ids. */ for (node = keyblock; node; node = node->next) { if (select_all || (node->flag & NODFLG_SELUID)) node->flag |= NODFLG_MARK_A; else node->flag &= ~NODFLG_MARK_A; } /* Reset mark for uids which are already signed. */ uidnode = NULL; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) { primary_pk = node->pkt->pkt.public_key; keyid_from_pk (primary_pk, pk_keyid); /* Is this a self-sig? */ if (pk_keyid[0] == sk_keyid[0] && pk_keyid[1] == sk_keyid[1]) selfsig = 1; } else if (node->pkt->pkttype == PKT_USER_ID) { uidnode = (node->flag & NODFLG_MARK_A) ? node : NULL; if (uidnode) { int yesreally = 0; char *user; user = utf8_to_native (uidnode->pkt->pkt.user_id->name, uidnode->pkt->pkt.user_id->len, 0); if (opt.only_sign_text_ids && uidnode->pkt->pkt.user_id->attribs) { tty_fprintf (fp, _("Skipping user ID \"%s\"," " which is not a text ID.\n"), user); uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; } else if (uidnode->pkt->pkt.user_id->flags.revoked) { tty_fprintf (fp, _("User ID \"%s\" is revoked."), user); if (selfsig) tty_fprintf (fp, "\n"); else if (opt.expert && !quick) { tty_fprintf (fp, "\n"); /* No, so remove the mark and continue */ if (!cpr_get_answer_is_yes ("sign_uid.revoke_okay", _("Are you sure you " "still want to sign " "it? (y/N) "))) { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; } else if (interactive) yesreally = 1; } else { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; tty_fprintf (fp, _(" Unable to sign.\n")); } } else if (uidnode->pkt->pkt.user_id->flags.expired) { tty_fprintf (fp, _("User ID \"%s\" is expired."), user); if (selfsig) tty_fprintf (fp, "\n"); else if (opt.expert && !quick) { tty_fprintf (fp, "\n"); /* No, so remove the mark and continue */ if (!cpr_get_answer_is_yes ("sign_uid.expire_okay", _("Are you sure you " "still want to sign " "it? (y/N) "))) { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; } else if (interactive) yesreally = 1; } else { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; tty_fprintf (fp, _(" Unable to sign.\n")); } } else if (!uidnode->pkt->pkt.user_id->created && !selfsig) { tty_fprintf (fp, _("User ID \"%s\" is not self-signed."), user); if (opt.expert && !quick) { tty_fprintf (fp, "\n"); /* No, so remove the mark and continue */ if (!cpr_get_answer_is_yes ("sign_uid.nosig_okay", _("Are you sure you " "still want to sign " "it? (y/N) "))) { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; } else if (interactive) yesreally = 1; } else { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; tty_fprintf (fp, _(" Unable to sign.\n")); } } if (uidnode && interactive && !yesreally && !quick) { tty_fprintf (fp, _("User ID \"%s\" is signable. "), user); if (!cpr_get_answer_is_yes ("sign_uid.sign_okay", _("Sign it? (y/N) "))) { uidnode->flag &= ~NODFLG_MARK_A; uidnode = NULL; } } xfree (user); } } else if (uidnode && node->pkt->pkttype == PKT_SIGNATURE && (node->pkt->pkt.signature->sig_class & ~3) == 0x10) { if (sk_keyid[0] == node->pkt->pkt.signature->keyid[0] && sk_keyid[1] == node->pkt->pkt.signature->keyid[1]) { char buf[50]; char *user; user = utf8_to_native (uidnode->pkt->pkt.user_id->name, uidnode->pkt->pkt.user_id->len, 0); /* It's a v3 self-sig. Make it into a v4 self-sig? */ if (node->pkt->pkt.signature->version < 4 && selfsig && !quick) { tty_fprintf (fp, _("The self-signature on \"%s\"\n" "is a PGP 2.x-style signature.\n"), user); /* Note that the regular PGP2 warning below still applies if there are no v4 sigs on this key at all. */ if (opt.expert) if (cpr_get_answer_is_yes ("sign_uid.v4_promote_okay", _("Do you want to promote " "it to an OpenPGP self-" "signature? (y/N) "))) { node->flag |= NODFLG_DELSIG; xfree (user); continue; } } /* Is the current signature expired? */ if (node->pkt->pkt.signature->flags.expired) { tty_fprintf (fp, _("Your current signature on \"%s\"\n" "has expired.\n"), user); if (quick || cpr_get_answer_is_yes ("sign_uid.replace_expired_okay", _("Do you want to issue a " "new signature to replace " "the expired one? (y/N) "))) { /* Mark these for later deletion. We don't want to delete them here, just in case the replacement signature doesn't happen for some reason. We only delete these after the replacement is already in place. */ node->flag |= NODFLG_DELSIG; xfree (user); continue; } } if (!node->pkt->pkt.signature->flags.exportable && !local) { /* It's a local sig, and we want to make a exportable sig. */ tty_fprintf (fp, _("Your current signature on \"%s\"\n" "is a local signature.\n"), user); if (quick || cpr_get_answer_is_yes ("sign_uid.local_promote_okay", _("Do you want to promote " "it to a full exportable " "signature? (y/N) "))) { /* Mark these for later deletion. We don't want to delete them here, just in case the replacement signature doesn't happen for some reason. We only delete these after the replacement is already in place. */ node->flag |= NODFLG_DELSIG; xfree (user); continue; } } /* Fixme: see whether there is a revocation in which * case we should allow signing it again. */ if (!node->pkt->pkt.signature->flags.exportable && local) tty_fprintf ( fp, _("\"%s\" was already locally signed by key %s\n"), user, keystr_from_pk (pk)); else tty_fprintf (fp, _("\"%s\" was already signed by key %s\n"), user, keystr_from_pk (pk)); if (opt.flags.force_sign_key || (opt.expert && !quick && cpr_get_answer_is_yes ("sign_uid.dupe_okay", _("Do you want to sign it " "again anyway? (y/N) ")))) { /* Don't delete the old sig here since this is an --expert thing. */ xfree (user); continue; } snprintf (buf, sizeof buf, "%08lX%08lX", (ulong) pk->keyid[0], (ulong) pk->keyid[1]); write_status_text (STATUS_ALREADY_SIGNED, buf); uidnode->flag &= ~NODFLG_MARK_A; /* remove mark */ xfree (user); } } } /* Check whether any uids are left for signing. */ if (!count_uids_with_flag (keyblock, NODFLG_MARK_A)) { tty_fprintf (fp, _("Nothing to sign with key %s\n"), keystr_from_pk (pk)); continue; } /* Ask whether we really should sign these user id(s). */ tty_fprintf (fp, "\n"); show_key_with_all_names (ctrl, fp, keyblock, 1, 0, 1, 0, 0, 0); tty_fprintf (fp, "\n"); if (primary_pk->expiredate && !selfsig) { /* Static analyzer note: A claim that PRIMARY_PK might be NULL is not correct because it set from the public key packet which is always the first packet in a keyblock and parsed in the above loop over the keyblock. In case the keyblock has no packets at all and thus the loop was not entered the above count_uids_with_flag would have detected this case. */ u32 now = make_timestamp (); if (primary_pk->expiredate <= now) { tty_fprintf (fp, _("This key has expired!")); if (opt.expert && !quick) { tty_fprintf (fp, " "); if (!cpr_get_answer_is_yes ("sign_uid.expired_okay", _("Are you sure you still " "want to sign it? (y/N) "))) continue; } else { tty_fprintf (fp, _(" Unable to sign.\n")); continue; } } else { tty_fprintf (fp, _("This key is due to expire on %s.\n"), expirestr_from_pk (primary_pk)); if (opt.ask_cert_expire && !quick) { char *answer = cpr_get ("sign_uid.expire", _("Do you want your signature to " "expire at the same time? (Y/n) ")); if (answer_is_yes_no_default (answer, 1)) { /* This fixes the signature timestamp we're going to make as now. This is so the expiration date is exactly correct, and not a few seconds off (due to the time it takes to answer the questions, enter the passphrase, etc). */ timestamp = now; duration = primary_pk->expiredate - now; } cpr_kill_prompt (); xfree (answer); } } } /* Only ask for duration if we haven't already set it to match the expiration of the pk */ if (!duration && !selfsig) { if (opt.ask_cert_expire && !quick) duration = ask_expire_interval (1, opt.def_cert_expire); else duration = parse_expire_string (opt.def_cert_expire); } if (selfsig) ; else { if (opt.batch || !opt.ask_cert_level || quick) class = 0x10 + opt.def_cert_level; else { char *answer; tty_fprintf (fp, _("How carefully have you verified the key you are " "about to sign actually belongs\nto the person " "named above? If you don't know what to " "answer, enter \"0\".\n")); tty_fprintf (fp, "\n"); tty_fprintf (fp, _(" (0) I will not answer.%s\n"), opt.def_cert_level == 0 ? " (default)" : ""); tty_fprintf (fp, _(" (1) I have not checked at all.%s\n"), opt.def_cert_level == 1 ? " (default)" : ""); tty_fprintf (fp, _(" (2) I have done casual checking.%s\n"), opt.def_cert_level == 2 ? " (default)" : ""); tty_fprintf (fp, _(" (3) I have done very careful checking.%s\n"), opt.def_cert_level == 3 ? " (default)" : ""); tty_fprintf (fp, "\n"); while (class == 0) { answer = cpr_get ("sign_uid.class", _("Your selection? " "(enter '?' for more information): ")); if (answer[0] == '\0') class = 0x10 + opt.def_cert_level; /* Default */ else if (ascii_strcasecmp (answer, "0") == 0) class = 0x10; /* Generic */ else if (ascii_strcasecmp (answer, "1") == 0) class = 0x11; /* Persona */ else if (ascii_strcasecmp (answer, "2") == 0) class = 0x12; /* Casual */ else if (ascii_strcasecmp (answer, "3") == 0) class = 0x13; /* Positive */ else tty_fprintf (fp, _("Invalid selection.\n")); xfree (answer); } } if (trust && !quick) trustsig_prompt (&trust_value, &trust_depth, &trust_regexp); } if (!quick) { p = get_user_id_native (ctrl, sk_keyid); tty_fprintf (fp, _("Are you sure that you want to sign this key with your\n" "key \"%s\" (%s)\n"), p, keystr_from_pk (pk)); xfree (p); } if (selfsig) { tty_fprintf (fp, "\n"); tty_fprintf (fp, _("This will be a self-signature.\n")); if (local) { tty_fprintf (fp, "\n"); tty_fprintf (fp, _("WARNING: the signature will not be marked " "as non-exportable.\n")); } if (nonrevocable) { tty_fprintf (fp, "\n"); tty_fprintf (fp, _("WARNING: the signature will not be marked " "as non-revocable.\n")); } } else { if (local) { tty_fprintf (fp, "\n"); tty_fprintf (fp, _("The signature will be marked as non-exportable.\n")); } if (nonrevocable) { tty_fprintf (fp, "\n"); tty_fprintf (fp, _("The signature will be marked as non-revocable.\n")); } switch (class) { case 0x11: tty_fprintf (fp, "\n"); tty_fprintf (fp, _("I have not checked this key at all.\n")); break; case 0x12: tty_fprintf (fp, "\n"); tty_fprintf (fp, _("I have checked this key casually.\n")); break; case 0x13: tty_fprintf (fp, "\n"); tty_fprintf (fp, _("I have checked this key very carefully.\n")); break; } } tty_fprintf (fp, "\n"); if (opt.batch && opt.answer_yes) ; else if (quick) ; else if (!cpr_get_answer_is_yes ("sign_uid.okay", _("Really sign? (y/N) "))) continue; /* Now we can sign the user ids. */ reloop: /* (Must use this, because we are modifying the list.) */ primary_pk = NULL; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) primary_pk = node->pkt->pkt.public_key; else if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_MARK_A)) { PACKET *pkt; PKT_signature *sig; struct sign_attrib attrib; log_assert (primary_pk); memset (&attrib, 0, sizeof attrib); attrib.non_exportable = local; attrib.non_revocable = nonrevocable; attrib.trust_depth = trust_depth; attrib.trust_value = trust_value; attrib.trust_regexp = trust_regexp; node->flag &= ~NODFLG_MARK_A; /* We force creation of a v4 signature for local * signatures, otherwise we would not generate the * subpacket with v3 keys and the signature becomes * exportable. */ if (selfsig) rc = make_keysig_packet (ctrl, &sig, primary_pk, node->pkt->pkt.user_id, NULL, pk, 0x13, 0, 0, keygen_add_std_prefs, primary_pk, NULL); else rc = make_keysig_packet (ctrl, &sig, primary_pk, node->pkt->pkt.user_id, NULL, pk, class, timestamp, duration, sign_mk_attrib, &attrib, NULL); if (rc) { write_status_error ("keysig", rc); log_error (_("signing failed: %s\n"), gpg_strerror (rc)); goto leave; } *ret_modified = 1; /* We changed the keyblock. */ update_trust = 1; pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (node, new_kbnode (pkt), PKT_SIGNATURE); goto reloop; } } /* Delete any sigs that got promoted */ for (node = keyblock; node; node = node->next) if (node->flag & NODFLG_DELSIG) delete_kbnode (node); } /* End loop over signators. */ leave: release_sk_list (sk_list); return rc; } /* * Change the passphrase of the primary and all secondary keys. Note * that it is common to use only one passphrase for the primary and * all subkeys. However, this is now (since GnuPG 2.1) all up to the * gpg-agent. Returns 0 on success or an error code. */ static gpg_error_t change_passphrase (ctrl_t ctrl, kbnode_t keyblock) { gpg_error_t err; kbnode_t node; PKT_public_key *pk; int any; u32 keyid[2], subid[2]; char *hexgrip = NULL; char *cache_nonce = NULL; char *passwd_nonce = NULL; node = find_kbnode (keyblock, PKT_PUBLIC_KEY); if (!node) { log_error ("Oops; public key missing!\n"); err = gpg_error (GPG_ERR_INTERNAL); goto leave; } pk = node->pkt->pkt.public_key; keyid_from_pk (pk, keyid); /* Check whether it is likely that we will be able to change the passphrase for any subkey. */ for (any = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { char *serialno; pk = node->pkt->pkt.public_key; keyid_from_pk (pk, subid); xfree (hexgrip); err = hexkeygrip_from_pk (pk, &hexgrip); if (err) goto leave; err = agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL); if (!err && serialno) ; /* Key on card. */ else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) ; /* Maybe stub key. */ else if (!err) any = 1; /* Key is known. */ else log_error ("key %s: error getting keyinfo from agent: %s\n", keystr_with_sub (keyid, subid), gpg_strerror (err)); xfree (serialno); } } err = 0; if (!any) { tty_printf (_("Key has only stub or on-card key items - " "no passphrase to change.\n")); goto leave; } /* Change the passphrase for all keys. */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { char *desc; pk = node->pkt->pkt.public_key; keyid_from_pk (pk, subid); xfree (hexgrip); err = hexkeygrip_from_pk (pk, &hexgrip); if (err) goto leave; /* Note that when using --dry-run we don't change the * passphrase but merely verify the current passphrase. */ desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_NORMAL, 1); err = agent_passwd (ctrl, hexgrip, desc, !!opt.dry_run, &cache_nonce, &passwd_nonce); xfree (desc); if (err) log_log ((gpg_err_code (err) == GPG_ERR_CANCELED || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) ? GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR, _("key %s: error changing passphrase: %s\n"), keystr_with_sub (keyid, subid), gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) break; } } leave: xfree (hexgrip); xfree (cache_nonce); xfree (passwd_nonce); return err; } /* Fix various problems in the keyblock. Returns true if the keyblock was changed. Note that a pointer to the keyblock must be given and the function may change it (i.e. replacing the first node). */ static int fix_keyblock (ctrl_t ctrl, kbnode_t *keyblockp) { int changed = 0; if (collapse_uids (keyblockp)) changed++; if (collapse_subkeys (keyblockp)) changed++; if (key_check_all_keysigs (ctrl, 1, *keyblockp, 0, 1)) changed++; reorder_keyblock (*keyblockp); /* If we modified the keyblock, make sure the flags are right. */ if (changed) merge_keys_and_selfsig (ctrl, *keyblockp); return changed; } static int parse_sign_type (const char *str, int *localsig, int *nonrevokesig, int *trustsig) { const char *p = str; while (*p) { if (ascii_strncasecmp (p, "l", 1) == 0) { *localsig = 1; p++; } else if (ascii_strncasecmp (p, "nr", 2) == 0) { *nonrevokesig = 1; p += 2; } else if (ascii_strncasecmp (p, "t", 1) == 0) { *trustsig = 1; p++; } else return 0; } return 1; } /* * Menu driven key editor. If seckey_check is true, then a secret key * that matches username will be looked for. If it is false, not all * commands will be available. * * Note: to keep track of certain selections we use node->mark MARKBIT_xxxx. */ /* Need an SK for this command */ #define KEYEDIT_NEED_SK 1 /* Need an SUB KEY for this command */ #define KEYEDIT_NEED_SUBSK 2 /* Match the tail of the string */ #define KEYEDIT_TAIL_MATCH 8 enum cmdids { cmdNONE = 0, cmdQUIT, cmdHELP, cmdFPR, cmdLIST, cmdSELUID, cmdCHECK, cmdSIGN, cmdREVSIG, cmdREVKEY, cmdREVUID, cmdDELSIG, cmdPRIMARY, cmdDEBUG, cmdSAVE, cmdADDUID, cmdADDPHOTO, cmdDELUID, cmdADDKEY, cmdDELKEY, cmdADDREVOKER, cmdTOGGLE, cmdSELKEY, cmdPASSWD, cmdTRUST, cmdPREF, cmdEXPIRE, cmdCHANGEUSAGE, cmdBACKSIGN, #ifndef NO_TRUST_MODELS cmdENABLEKEY, cmdDISABLEKEY, #endif /*!NO_TRUST_MODELS*/ cmdSHOWPREF, cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST, cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdKEYTOTPM, cmdBKUPTOCARD, cmdCLEAN, cmdMINIMIZE, cmdGRIP, cmdNOP }; static struct { const char *name; enum cmdids id; int flags; const char *desc; } cmds[] = { { "quit", cmdQUIT, 0, N_("quit this menu")}, { "q", cmdQUIT, 0, NULL}, { "save", cmdSAVE, 0, N_("save and quit")}, { "help", cmdHELP, 0, N_("show this help")}, { "?", cmdHELP, 0, NULL}, { "fpr", cmdFPR, 0, N_("show key fingerprint")}, { "grip", cmdGRIP, 0, N_("show the keygrip")}, { "list", cmdLIST, 0, N_("list key and user IDs")}, { "l", cmdLIST, 0, NULL}, { "uid", cmdSELUID, 0, N_("select user ID N")}, { "key", cmdSELKEY, 0, N_("select subkey N")}, { "check", cmdCHECK, 0, N_("check signatures")}, { "c", cmdCHECK, 0, NULL}, { "change-usage", cmdCHANGEUSAGE, KEYEDIT_NEED_SK, NULL}, { "cross-certify", cmdBACKSIGN, KEYEDIT_NEED_SK, NULL}, { "backsign", cmdBACKSIGN, KEYEDIT_NEED_SK, NULL}, { "sign", cmdSIGN, KEYEDIT_TAIL_MATCH, N_("sign selected user IDs [* see below for related commands]")}, { "s", cmdSIGN, 0, NULL}, /* "lsign" and friends will never match since "sign" comes first and it is a tail match. They are just here so they show up in the help menu. */ { "lsign", cmdNOP, 0, N_("sign selected user IDs locally")}, { "tsign", cmdNOP, 0, N_("sign selected user IDs with a trust signature")}, { "nrsign", cmdNOP, 0, N_("sign selected user IDs with a non-revocable signature")}, { "debug", cmdDEBUG, 0, NULL}, { "adduid", cmdADDUID, KEYEDIT_NEED_SK, N_("add a user ID")}, { "addphoto", cmdADDPHOTO, KEYEDIT_NEED_SK, N_("add a photo ID")}, { "deluid", cmdDELUID, 0, N_("delete selected user IDs")}, /* delphoto is really deluid in disguise */ { "delphoto", cmdDELUID, 0, NULL}, { "addkey", cmdADDKEY, KEYEDIT_NEED_SK, N_("add a subkey")}, #ifdef ENABLE_CARD_SUPPORT { "addcardkey", cmdADDCARDKEY, KEYEDIT_NEED_SK, N_("add a key to a smartcard")}, { "keytocard", cmdKEYTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, N_("move a key to a smartcard")}, { "keytotpm", cmdKEYTOTPM, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, N_("convert a key to TPM form using the local TPM")}, { "bkuptocard", cmdBKUPTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, N_("move a backup key to a smartcard")}, #endif /*ENABLE_CARD_SUPPORT */ { "delkey", cmdDELKEY, 0, N_("delete selected subkeys")}, { "addrevoker", cmdADDREVOKER, KEYEDIT_NEED_SK, N_("add a revocation key")}, { "delsig", cmdDELSIG, 0, N_("delete signatures from the selected user IDs")}, { "expire", cmdEXPIRE, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, N_("change the expiration date for the key or selected subkeys")}, { "primary", cmdPRIMARY, KEYEDIT_NEED_SK, N_("flag the selected user ID as primary")}, { "toggle", cmdTOGGLE, KEYEDIT_NEED_SK, NULL}, /* Dummy command. */ { "t", cmdTOGGLE, KEYEDIT_NEED_SK, NULL}, { "pref", cmdPREF, 0, N_("list preferences (expert)")}, { "showpref", cmdSHOWPREF, 0, N_("list preferences (verbose)")}, { "setpref", cmdSETPREF, KEYEDIT_NEED_SK, N_("set preference list for the selected user IDs")}, { "updpref", cmdSETPREF, KEYEDIT_NEED_SK, NULL}, { "keyserver", cmdPREFKS, KEYEDIT_NEED_SK, N_("set the preferred keyserver URL for the selected user IDs")}, { "notation", cmdNOTATION, KEYEDIT_NEED_SK, N_("set a notation for the selected user IDs")}, { "passwd", cmdPASSWD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, N_("change the passphrase")}, { "password", cmdPASSWD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, NULL}, #ifndef NO_TRUST_MODELS { "trust", cmdTRUST, 0, N_("change the ownertrust")}, #endif /*!NO_TRUST_MODELS*/ { "revsig", cmdREVSIG, 0, N_("revoke signatures on the selected user IDs")}, { "revuid", cmdREVUID, KEYEDIT_NEED_SK, N_("revoke selected user IDs")}, { "revphoto", cmdREVUID, KEYEDIT_NEED_SK, NULL}, { "revkey", cmdREVKEY, KEYEDIT_NEED_SK, N_("revoke key or selected subkeys")}, #ifndef NO_TRUST_MODELS { "enable", cmdENABLEKEY, 0, N_("enable key")}, { "disable", cmdDISABLEKEY, 0, N_("disable key")}, #endif /*!NO_TRUST_MODELS*/ { "showphoto", cmdSHOWPHOTO, 0, N_("show selected photo IDs")}, { "clean", cmdCLEAN, 0, N_("compact unusable user IDs and remove unusable signatures from key")}, { "minimize", cmdMINIMIZE, 0, N_("compact unusable user IDs and remove all signatures from key")}, { NULL, cmdNONE, 0, NULL} }; #ifdef HAVE_LIBREADLINE /* These two functions are used by readline for command completion. */ static char * command_generator (const char *text, int state) { static int list_index, len; const char *name; /* If this is a new word to complete, initialize now. This includes saving the length of TEXT for efficiency, and initializing the index variable to 0. */ if (!state) { list_index = 0; len = strlen (text); } /* Return the next partial match */ while ((name = cmds[list_index].name)) { /* Only complete commands that have help text */ if (cmds[list_index++].desc && strncmp (name, text, len) == 0) return strdup (name); } return NULL; } static char ** keyedit_completion (const char *text, int start, int end) { /* If we are at the start of a line, we try and command-complete. If not, just do nothing for now. */ (void) end; if (start == 0) return rl_completion_matches (text, command_generator); rl_attempted_completion_over = 1; return NULL; } #endif /* HAVE_LIBREADLINE */ /* Main function of the menu driven key editor. */ void keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, strlist_t commands, int quiet, int seckey_check) { enum cmdids cmd = 0; gpg_error_t err = 0; KBNODE keyblock = NULL; KEYDB_HANDLE kdbhd = NULL; int have_seckey = 0; int have_anyseckey = 0; char *answer = NULL; int redisplay = 1; int modified = 0; int sec_shadowing = 0; int run_subkey_warnings = 0; int have_commands = !!commands; if (opt.command_fd != -1) ; else if (opt.batch && !have_commands) { log_error (_("can't do this in batch mode\n")); goto leave; } #ifdef HAVE_W32_SYSTEM /* Due to Windows peculiarities we need to make sure that the trustdb stale check is done before we open another file (i.e. by searching for a key). In theory we could make sure that the files are closed after use but the open/close caches inhibits that and flushing the cache right before the stale check is not easy to implement. Thus we take the easy way out and run the stale check as early as possible. Note, that for non- W32 platforms it is run indirectly trough a call to get_validity (). */ check_trustdb_stale (ctrl); #endif /* Get the public key */ err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, NULL, NULL, username, &keyblock, &kdbhd, 1); if (err) { log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err)); goto leave; } if (fix_keyblock (ctrl, &keyblock)) modified++; /* See whether we have a matching secret key. */ if (seckey_check) { have_anyseckey = !agent_probe_any_secret_key (ctrl, keyblock); if (have_anyseckey && agent_probe_secret_key (ctrl, keyblock->pkt->pkt.public_key)) { /* The primary key is also available. */ have_seckey = 1; } if (have_seckey && !quiet) tty_printf (_("Secret key is available.\n")); else if (have_anyseckey && !quiet) tty_printf (_("Secret subkeys are available.\n")); } /* Main command loop. */ for (;;) { int i, arg_number, photo; const char *arg_string = ""; char *p; PKT_public_key *pk = keyblock->pkt->pkt.public_key; tty_printf ("\n"); if (redisplay && !quiet) { /* Show using flags: with_revoker, with_subkeys. */ show_key_with_all_names (ctrl, NULL, keyblock, 0, 1, 0, 1, 0, 0); tty_printf ("\n"); redisplay = 0; } if (run_subkey_warnings) { run_subkey_warnings = 0; if (!count_selected_keys (keyblock)) subkey_expire_warning (keyblock); } do { xfree (answer); if (have_commands) { if (commands) { answer = xstrdup (commands->d); commands = commands->next; } else if (opt.batch) { answer = xstrdup ("quit"); } else have_commands = 0; } if (!have_commands) { #ifdef HAVE_LIBREADLINE tty_enable_completion (keyedit_completion); #endif answer = cpr_get_no_help ("keyedit.prompt", GPG_NAME "> "); cpr_kill_prompt (); tty_disable_completion (); } trim_spaces (answer); } while (*answer == '#'); arg_number = 0; /* Here is the init which egcc complains about. */ photo = 0; /* Same here. */ if (!*answer) cmd = cmdLIST; else if (*answer == CONTROL_D) cmd = cmdQUIT; else if (digitp (answer)) { cmd = cmdSELUID; arg_number = atoi (answer); } else { if ((p = strchr (answer, ' '))) { *p++ = 0; trim_spaces (answer); trim_spaces (p); arg_number = atoi (p); arg_string = p; } for (i = 0; cmds[i].name; i++) { if (cmds[i].flags & KEYEDIT_TAIL_MATCH) { size_t l = strlen (cmds[i].name); size_t a = strlen (answer); if (a >= l) { if (!ascii_strcasecmp (&answer[a - l], cmds[i].name)) { answer[a - l] = '\0'; break; } } } else if (!ascii_strcasecmp (answer, cmds[i].name)) break; } if ((cmds[i].flags & (KEYEDIT_NEED_SK|KEYEDIT_NEED_SUBSK)) && !(((cmds[i].flags & KEYEDIT_NEED_SK) && have_seckey) || ((cmds[i].flags & KEYEDIT_NEED_SUBSK) && have_anyseckey))) { tty_printf (_("Need the secret key to do this.\n")); cmd = cmdNOP; } else cmd = cmds[i].id; } /* Dispatch the command. */ switch (cmd) { case cmdHELP: for (i = 0; cmds[i].name; i++) { if ((cmds[i].flags & (KEYEDIT_NEED_SK|KEYEDIT_NEED_SUBSK)) && !(((cmds[i].flags & KEYEDIT_NEED_SK) && have_seckey) ||((cmds[i].flags&KEYEDIT_NEED_SUBSK)&&have_anyseckey))) ; /* Skip those item if we do not have the secret key. */ else if (cmds[i].desc) tty_printf ("%-11s %s\n", cmds[i].name, _(cmds[i].desc)); } tty_printf ("\n"); tty_printf (_("* The 'sign' command may be prefixed with an 'l' for local " "signatures (lsign),\n" " a 't' for trust signatures (tsign), an 'nr' for " "non-revocable signatures\n" " (nrsign), or any combination thereof (ltsign, " "tnrsign, etc.).\n")); break; case cmdLIST: redisplay = 1; break; case cmdFPR: show_key_and_fingerprint (ctrl, keyblock, (*arg_string == '*' && (!arg_string[1] || spacep (arg_string + 1)))); break; case cmdGRIP: show_key_and_grip (keyblock); break; case cmdSELUID: if (strlen (arg_string) == NAMEHASH_LEN * 2) redisplay = menu_select_uid_namehash (keyblock, arg_string); else { if (*arg_string == '*' && (!arg_string[1] || spacep (arg_string + 1))) arg_number = -1; /* Select all. */ redisplay = menu_select_uid (keyblock, arg_number); } break; case cmdSELKEY: { if (*arg_string == '*' && (!arg_string[1] || spacep (arg_string + 1))) arg_number = -1; /* Select all. */ if (menu_select_key (keyblock, arg_number, p)) redisplay = 1; } break; case cmdCHECK: if (key_check_all_keysigs (ctrl, -1, keyblock, count_selected_uids (keyblock), !strcmp (arg_string, "selfsig"))) modified = 1; break; case cmdSIGN: { int localsig = 0, nonrevokesig = 0, trustsig = 0, interactive = 0; if (pk->flags.revoked) { tty_printf (_("Key is revoked.")); if (opt.expert) { tty_printf (" "); if (!cpr_get_answer_is_yes ("keyedit.sign_revoked.okay", _("Are you sure you still want to sign it? (y/N) "))) break; } else { tty_printf (_(" Unable to sign.\n")); break; } } if (count_uids (keyblock) > 1 && !count_selected_uids (keyblock)) { int result; if (opt.only_sign_text_ids) result = cpr_get_answer_is_yes ("keyedit.sign_all.okay", _("Really sign all text user IDs? (y/N) ")); else result = cpr_get_answer_is_yes ("keyedit.sign_all.okay", _("Really sign all user IDs? (y/N) ")); if (! result) { if (opt.interactive) interactive = 1; else { tty_printf (_("Hint: Select the user IDs to sign\n")); have_commands = 0; break; } } } /* What sort of signing are we doing? */ if (!parse_sign_type (answer, &localsig, &nonrevokesig, &trustsig)) { tty_printf (_("Unknown signature type '%s'\n"), answer); break; } sign_uids (ctrl, NULL, keyblock, locusr, &modified, localsig, nonrevokesig, trustsig, interactive, 0); } break; case cmdDEBUG: dump_kbnode (keyblock); break; case cmdTOGGLE: /* The toggle command is a leftover from old gpg versions where we worked with a secret and a public keyring. It is not necessary anymore but we keep this command for the sake of scripts using it. */ redisplay = 1; break; case cmdADDPHOTO: if (RFC2440) { tty_printf (_("This command is not allowed while in %s mode.\n"), gnupg_compliance_option_string (opt.compliance)); break; } photo = 1; /* fall through */ case cmdADDUID: if (menu_adduid (ctrl, keyblock, photo, arg_string, NULL)) { update_trust = 1; redisplay = 1; modified = 1; merge_keys_and_selfsig (ctrl, keyblock); } break; case cmdDELUID: { int n1; if (!(n1 = count_selected_uids (keyblock))) { tty_printf (_("You must select at least one user ID.\n")); if (!opt.expert) tty_printf (_("(Use the '%s' command.)\n"), "uid"); } else if (real_uids_left (keyblock) < 1) tty_printf (_("You can't delete the last user ID!\n")); else if (cpr_get_answer_is_yes ("keyedit.remove.uid.okay", n1 > 1 ? _("Really remove all selected user IDs? (y/N) ") : _("Really remove this user ID? (y/N) "))) { menu_deluid (keyblock); redisplay = 1; modified = 1; } } break; case cmdDELSIG: { int n1; if (!(n1 = count_selected_uids (keyblock))) { tty_printf (_("You must select at least one user ID.\n")); if (!opt.expert) tty_printf (_("(Use the '%s' command.)\n"), "uid"); } else if (menu_delsig (ctrl, keyblock)) { /* No redisplay here, because it may scroll away some * of the status output of this command. */ modified = 1; } } break; case cmdADDKEY: if (!generate_subkeypair (ctrl, keyblock, NULL, NULL, NULL)) { redisplay = 1; modified = 1; merge_keys_and_selfsig (ctrl, keyblock); } break; #ifdef ENABLE_CARD_SUPPORT case cmdADDCARDKEY: if (!card_generate_subkey (ctrl, keyblock)) { redisplay = 1; modified = 1; merge_keys_and_selfsig (ctrl, keyblock); } break; case cmdKEYTOTPM: /* FIXME need to store the key and not commit until later */ { kbnode_t node = NULL; switch (count_selected_keys (keyblock)) { case 0: if (cpr_get_answer_is_yes ("keyedit.keytocard.use_primary", /* TRANSLATORS: Please take care: This is about moving the key and not about removing it. */ _("Really move the primary key? (y/N) "))) node = keyblock; break; case 1: for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY && node->flag & NODFLG_SELKEY) break; } break; default: tty_printf (_("You must select exactly one key.\n")); break; } if (node) { PKT_public_key *xxpk = node->pkt->pkt.public_key; char *hexgrip; hexkeygrip_from_pk (xxpk, &hexgrip); if (!agent_keytotpm (ctrl, hexgrip)) { redisplay = 1; } xfree (hexgrip); } } break; case cmdKEYTOCARD: { KBNODE node = NULL; switch (count_selected_keys (keyblock)) { case 0: if (cpr_get_answer_is_yes ("keyedit.keytocard.use_primary", /* TRANSLATORS: Please take care: This is about moving the key and not about removing it. */ _("Really move the primary key? (y/N) "))) node = keyblock; break; case 1: for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY && node->flag & NODFLG_SELKEY) break; } break; default: tty_printf (_("You must select exactly one key.\n")); break; } if (node) { PKT_public_key *xxpk = node->pkt->pkt.public_key; if (card_store_subkey (node, xxpk ? xxpk->pubkey_usage : 0)) { redisplay = 1; sec_shadowing = 1; } } } break; case cmdBKUPTOCARD: { /* Ask for a filename, check whether this is really a backup key as generated by the card generation, parse that key and store it on card. */ KBNODE node; char *fname; PACKET *pkt; IOBUF a; struct parse_packet_ctx_s parsectx; if (!*arg_string) { tty_printf (_("Command expects a filename argument\n")); break; } if (*arg_string == DIRSEP_C) fname = xstrdup (arg_string); else if (*arg_string == '~') fname = make_filename (arg_string, NULL); else fname = make_filename (gnupg_homedir (), arg_string, NULL); /* Open that file. */ a = iobuf_open (fname); if (a && is_secured_file (iobuf_get_fd (a))) { iobuf_close (a); a = NULL; gpg_err_set_errno (EPERM); } if (!a) { tty_printf (_("Can't open '%s': %s\n"), fname, strerror (errno)); xfree (fname); break; } /* Parse and check that file. */ pkt = xmalloc (sizeof *pkt); init_packet (pkt); init_parse_packet (&parsectx, a); err = parse_packet (&parsectx, pkt); deinit_parse_packet (&parsectx); iobuf_close (a); iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char *) fname); if (!err && pkt->pkttype != PKT_SECRET_KEY && pkt->pkttype != PKT_SECRET_SUBKEY) err = GPG_ERR_NO_SECKEY; if (err) { tty_printf (_("Error reading backup key from '%s': %s\n"), fname, gpg_strerror (err)); xfree (fname); free_packet (pkt, NULL); xfree (pkt); break; } xfree (fname); node = new_kbnode (pkt); /* Transfer it to gpg-agent which handles secret keys. */ err = transfer_secret_keys (ctrl, NULL, node, 1, 1, 0); /* Treat the pkt as a public key. */ pkt->pkttype = PKT_PUBLIC_KEY; /* Ask gpg-agent to store the secret key to card. */ if (card_store_subkey (node, 0)) { redisplay = 1; sec_shadowing = 1; } release_kbnode (node); } break; #endif /* ENABLE_CARD_SUPPORT */ case cmdDELKEY: { int n1; if (!(n1 = count_selected_keys (keyblock))) { tty_printf (_("You must select at least one key.\n")); if (!opt.expert) tty_printf (_("(Use the '%s' command.)\n"), "key"); } else if (!cpr_get_answer_is_yes ("keyedit.remove.subkey.okay", n1 > 1 ? _("Do you really want to delete the " "selected keys? (y/N) ") : _("Do you really want to delete this key? (y/N) "))) ; else { menu_delkey (keyblock); redisplay = 1; modified = 1; } } break; case cmdADDREVOKER: { int sensitive = 0; if (ascii_strcasecmp (arg_string, "sensitive") == 0) sensitive = 1; if (menu_addrevoker (ctrl, keyblock, sensitive)) { redisplay = 1; modified = 1; merge_keys_and_selfsig (ctrl, keyblock); } } break; case cmdREVUID: { int n1; if (!(n1 = count_selected_uids (keyblock))) { tty_printf (_("You must select at least one user ID.\n")); if (!opt.expert) tty_printf (_("(Use the '%s' command.)\n"), "uid"); } else if (cpr_get_answer_is_yes ("keyedit.revoke.uid.okay", n1 > 1 ? _("Really revoke all selected user IDs? (y/N) ") : _("Really revoke this user ID? (y/N) "))) { if (menu_revuid (ctrl, keyblock)) { modified = 1; redisplay = 1; } } } break; case cmdREVKEY: { int n1; if (!(n1 = count_selected_keys (keyblock))) { if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay", _("Do you really want to revoke" " the entire key? (y/N) "))) { if (menu_revkey (ctrl, keyblock)) modified = 1; redisplay = 1; } } else if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay", n1 > 1 ? _("Do you really want to revoke" " the selected subkeys? (y/N) ") : _("Do you really want to revoke" " this subkey? (y/N) "))) { if (menu_revsubkey (ctrl, keyblock)) modified = 1; redisplay = 1; } if (modified) merge_keys_and_selfsig (ctrl, keyblock); } break; case cmdEXPIRE: if (gpg_err_code (menu_expire (ctrl, keyblock, 0, 0)) == GPG_ERR_TRUE) { merge_keys_and_selfsig (ctrl, keyblock); run_subkey_warnings = 1; modified = 1; redisplay = 1; } break; case cmdCHANGEUSAGE: if (menu_changeusage (ctrl, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); modified = 1; redisplay = 1; } break; case cmdBACKSIGN: if (menu_backsign (ctrl, keyblock)) { modified = 1; redisplay = 1; } break; case cmdPRIMARY: if (menu_set_primary_uid (ctrl, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); modified = 1; redisplay = 1; } break; case cmdPASSWD: change_passphrase (ctrl, keyblock); break; #ifndef NO_TRUST_MODELS case cmdTRUST: if (opt.trust_model == TM_EXTERNAL) { tty_printf (_("Owner trust may not be set while " "using a user provided trust database\n")); break; } show_key_with_all_names (ctrl, NULL, keyblock, 0, 0, 0, 1, 0, 0); tty_printf ("\n"); if (edit_ownertrust (ctrl, find_kbnode (keyblock, PKT_PUBLIC_KEY)->pkt->pkt. public_key, 1)) { redisplay = 1; /* No real need to set update_trust here as edit_ownertrust() calls revalidation_mark() anyway. */ update_trust = 1; } break; #endif /*!NO_TRUST_MODELS*/ case cmdPREF: { int count = count_selected_uids (keyblock); log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key, count ? NODFLG_SELUID : 0, 1); } break; case cmdSHOWPREF: { int count = count_selected_uids (keyblock); log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key, count ? NODFLG_SELUID : 0, 2); } break; case cmdSETPREF: { PKT_user_id *tempuid; keygen_set_std_prefs (!*arg_string ? "default" : arg_string, 0); tempuid = keygen_get_std_prefs (); tty_printf (_("Set preference list to:\n")); show_prefs (tempuid, NULL, 1); free_user_id (tempuid); if (cpr_get_answer_is_yes ("keyedit.setpref.okay", count_selected_uids (keyblock) ? _("Really update the preferences" " for the selected user IDs? (y/N) ") : _("Really update the preferences? (y/N) "))) { if (menu_set_preferences (ctrl, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); modified = 1; redisplay = 1; } } } break; case cmdPREFKS: if (menu_set_keyserver_url (ctrl, *arg_string ? arg_string : NULL, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); modified = 1; redisplay = 1; } break; case cmdNOTATION: if (menu_set_notation (ctrl, *arg_string ? arg_string : NULL, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); modified = 1; redisplay = 1; } break; case cmdNOP: break; case cmdREVSIG: if (menu_revsig (ctrl, keyblock)) { redisplay = 1; modified = 1; } break; #ifndef NO_TRUST_MODELS case cmdENABLEKEY: case cmdDISABLEKEY: if (enable_disable_key (ctrl, keyblock, cmd == cmdDISABLEKEY)) { redisplay = 1; modified = 1; } break; #endif /*!NO_TRUST_MODELS*/ case cmdSHOWPHOTO: menu_showphoto (ctrl, keyblock); break; case cmdCLEAN: if (menu_clean (ctrl, keyblock, 0)) redisplay = modified = 1; break; case cmdMINIMIZE: if (menu_clean (ctrl, keyblock, 1)) redisplay = modified = 1; break; case cmdQUIT: if (have_commands) goto leave; if (!modified && !sec_shadowing) goto leave; if (!cpr_get_answer_is_yes ("keyedit.save.okay", _("Save changes? (y/N) "))) { if (cpr_enabled () || cpr_get_answer_is_yes ("keyedit.cancel.okay", _("Quit without saving? (y/N) "))) goto leave; break; } /* fall through */ case cmdSAVE: if (modified) { err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); break; } } if (sec_shadowing) { err = agent_scd_learn (NULL, 1); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); break; } } if (!modified && !sec_shadowing) tty_printf (_("Key not changed so no update needed.\n")); if (update_trust) { revalidation_mark (ctrl); update_trust = 0; } goto leave; case cmdINVCMD: default: tty_printf ("\n"); tty_printf (_("Invalid command (try \"help\")\n")); break; } } /* End of the main command loop. */ leave: release_kbnode (keyblock); keydb_release (kdbhd); xfree (answer); } /* Change the passphrase of the secret key identified by USERNAME. */ void keyedit_passwd (ctrl_t ctrl, const char *username) { gpg_error_t err; PKT_public_key *pk; kbnode_t keyblock = NULL; pk = xtrycalloc (1, sizeof *pk); if (!pk) { err = gpg_error_from_syserror (); goto leave; } err = getkey_byname (ctrl, NULL, pk, username, 1, &keyblock); if (err) goto leave; err = change_passphrase (ctrl, keyblock); leave: release_kbnode (keyblock); free_public_key (pk); if (err) { log_info ("error changing the passphrase for '%s': %s\n", username, gpg_strerror (err)); write_status_error ("keyedit.passwd", err); } else write_status_text (STATUS_SUCCESS, "keyedit.passwd"); } /* Helper for quick commands to find the keyblock for USERNAME. * Returns on success the key database handle at R_KDBHD and the * keyblock at R_KEYBLOCK. */ static gpg_error_t quick_find_keyblock (ctrl_t ctrl, const char *username, int want_secret, KEYDB_HANDLE *r_kdbhd, kbnode_t *r_keyblock) { gpg_error_t err; KEYDB_HANDLE kdbhd = NULL; kbnode_t keyblock = NULL; KEYDB_SEARCH_DESC desc; kbnode_t node; *r_kdbhd = NULL; *r_keyblock = NULL; /* Search the key; we don't want the whole getkey stuff here. */ kdbhd = keydb_new (ctrl); if (!kdbhd) { /* Note that keydb_new has already used log_error. */ err = gpg_error_from_syserror (); goto leave; } err = classify_user_id (username, &desc, 1); if (!err) err = keydb_search (kdbhd, &desc, 1, NULL); if (!err) { err = keydb_get_keyblock (kdbhd, &keyblock); if (err) { log_error (_("error reading keyblock: %s\n"), gpg_strerror (err)); goto leave; } /* Now with the keyblock retrieved, search again to detect an ambiguous specification. We need to save the found state so that we can do an update later. */ keydb_push_found_state (kdbhd); err = keydb_search (kdbhd, &desc, 1, NULL); if (!err) err = gpg_error (GPG_ERR_AMBIGUOUS_NAME); else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = 0; keydb_pop_found_state (kdbhd); if (!err && want_secret) { /* We require the secret primary key to set the primary UID. */ node = find_kbnode (keyblock, PKT_PUBLIC_KEY); log_assert (node); if (!agent_probe_secret_key (ctrl, node->pkt->pkt.public_key)) err = gpg_error (GPG_ERR_NO_SECKEY); } } else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = gpg_error (GPG_ERR_NO_PUBKEY); if (err) { log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err)); goto leave; } fix_keyblock (ctrl, &keyblock); merge_keys_and_selfsig (ctrl, keyblock); *r_keyblock = keyblock; keyblock = NULL; *r_kdbhd = kdbhd; kdbhd = NULL; leave: release_kbnode (keyblock); keydb_release (kdbhd); return err; } /* Unattended adding of a new keyid. USERNAME specifies the key. NEWUID is the new user id to add to the key. */ void keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid) { gpg_error_t err; KEYDB_HANDLE kdbhd = NULL; kbnode_t keyblock = NULL; char *uidstring = NULL; uidstring = xstrdup (newuid); trim_spaces (uidstring); if (!*uidstring) { log_error ("%s\n", gpg_strerror (GPG_ERR_INV_USER_ID)); goto leave; } #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* Search the key; we don't want the whole getkey stuff here. */ err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock); if (err) goto leave; if (menu_adduid (ctrl, keyblock, 0, NULL, uidstring)) { err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } if (update_trust) revalidation_mark (ctrl); } leave: xfree (uidstring); release_kbnode (keyblock); keydb_release (kdbhd); } /* Helper to find the UID node for namehash. On success, returns the UID node. Otherwise, return NULL. */ kbnode_t find_userid_by_namehash (kbnode_t keyblock, const char *namehash, int want_valid) { byte hash[NAMEHASH_LEN]; kbnode_t node = NULL; if (!namehash) goto leave; if (strlen (namehash) != NAMEHASH_LEN * 2) goto leave; if (hex2bin (namehash, hash, NAMEHASH_LEN) < 0) goto leave; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && (!want_valid || (!node->pkt->pkt.user_id->flags.revoked && !node->pkt->pkt.user_id->flags.expired))) { namehash_from_uid (node->pkt->pkt.user_id); if (!memcmp (node->pkt->pkt.user_id->namehash, hash, NAMEHASH_LEN)) break; } } leave: return node; } /* Helper to find the UID node for uid. On success, returns the UID node. Otherwise, return NULL. */ kbnode_t find_userid (kbnode_t keyblock, const char *uid, int want_valid) { kbnode_t node = NULL; size_t uidlen; if (!keyblock || !uid) goto leave; /* First try to find UID by namehash. */ node = find_userid_by_namehash (keyblock, uid, want_valid); if (node) goto leave; uidlen = strlen (uid); for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && (!want_valid || (!node->pkt->pkt.user_id->flags.revoked && !node->pkt->pkt.user_id->flags.expired)) && uidlen == node->pkt->pkt.user_id->len && !memcmp (node->pkt->pkt.user_id->name, uid, uidlen)) break; } leave: return node; } /* Unattended revocation of a keyid. USERNAME specifies the key. UIDTOREV is the user id revoke from the key. */ void keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev) { gpg_error_t err; KEYDB_HANDLE kdbhd = NULL; kbnode_t keyblock = NULL; kbnode_t node; int modified = 0; size_t valid_uids; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* Search the key; we don't want the whole getkey stuff here. */ err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock); if (err) goto leave; /* To make sure that we do not revoke the last valid UID, we first count how many valid UIDs there are. */ valid_uids = 0; for (node = keyblock; node; node = node->next) valid_uids += (node->pkt->pkttype == PKT_USER_ID && !node->pkt->pkt.user_id->flags.revoked && !node->pkt->pkt.user_id->flags.expired); /* Find the right UID. */ node = find_userid (keyblock, uidtorev, 0); if (node) { struct revocation_reason_info *reason; /* Make sure that we do not revoke the last valid UID. */ if (valid_uids == 1 && ! node->pkt->pkt.user_id->flags.revoked && ! node->pkt->pkt.user_id->flags.expired) { log_error (_("cannot revoke the last valid user ID.\n")); err = gpg_error (GPG_ERR_INV_USER_ID); goto leave; } reason = get_default_uid_revocation_reason (); err = core_revuid (ctrl, keyblock, node, reason, &modified); release_revocation_reason_info (reason); if (err) goto leave; err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } revalidation_mark (ctrl); goto leave; } err = gpg_error (GPG_ERR_NO_USER_ID); leave: if (err) { log_error (_("revoking the user ID failed: %s\n"), gpg_strerror (err)); write_status_error ("keyedit.revoke.uid", err); } release_kbnode (keyblock); keydb_release (kdbhd); } /* Unattended setting of the primary uid. USERNAME specifies the key. PRIMARYUID is the user id which shall be primary. */ void keyedit_quick_set_primary (ctrl_t ctrl, const char *username, const char *primaryuid) { gpg_error_t err; KEYDB_HANDLE kdbhd = NULL; kbnode_t keyblock = NULL; kbnode_t primarynode; kbnode_t node; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock); if (err) { write_status_error ("keyedit.primary", err); goto leave; } /* Find the first matching UID that is valid */ primarynode = find_userid (keyblock, primaryuid, 1); /* and mark it. */ if (primarynode) for (node = keyblock; node; node = node->next) { if (node == primarynode) node->flag |= NODFLG_SELUID; else node->flag &= ~NODFLG_SELUID; } if (!primarynode) err = gpg_error (GPG_ERR_NO_USER_ID); else if (menu_set_primary_uid (ctrl, keyblock)) { merge_keys_and_selfsig (ctrl, keyblock); err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } revalidation_mark (ctrl); } else err = gpg_error (GPG_ERR_GENERAL); if (err) { log_error (_("setting the primary user ID failed: %s\n"), gpg_strerror (err)); write_status_error ("keyedit.primary", err); } leave: release_kbnode (keyblock); keydb_release (kdbhd); } /* Find a keyblock by fingerprint because only this uniquely * identifies a key and may thus be used to select a key for * unattended subkey creation os key signing. */ static gpg_error_t find_by_primary_fpr (ctrl_t ctrl, const char *fpr, kbnode_t *r_keyblock, KEYDB_HANDLE *r_kdbhd) { gpg_error_t err; kbnode_t keyblock = NULL; KEYDB_HANDLE kdbhd = NULL; KEYDB_SEARCH_DESC desc; byte fprbin[MAX_FINGERPRINT_LEN]; size_t fprlen; *r_keyblock = NULL; *r_kdbhd = NULL; if (classify_user_id (fpr, &desc, 1) || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_error (_("\"%s\" is not a fingerprint\n"), fpr); err = gpg_error (GPG_ERR_INV_NAME); goto leave; } err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, NULL, NULL, fpr, &keyblock, &kdbhd, 1); if (err) { log_error (_("key \"%s\" not found: %s\n"), fpr, gpg_strerror (err)); goto leave; } /* Check that the primary fingerprint has been given. */ fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen); if (desc.mode == KEYDB_SEARCH_MODE_FPR && fprlen == desc.fprlen && !memcmp (fprbin, desc.u.fpr, fprlen)) ; else { log_error (_("\"%s\" is not the primary fingerprint\n"), fpr); err = gpg_error (GPG_ERR_INV_NAME); goto leave; } *r_keyblock = keyblock; keyblock = NULL; *r_kdbhd = kdbhd; kdbhd = NULL; err = 0; leave: release_kbnode (keyblock); keydb_release (kdbhd); return err; } /* Unattended key signing function. If the key specifified by FPR is available and FPR is the primary fingerprint all user ids of the key are signed using the default signing key. If UIDS is an empty list all usable UIDs are signed, if it is not empty, only those user ids matching one of the entries of the list are signed. With LOCAL being true the signatures are marked as non-exportable. */ void keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids, strlist_t locusr, int local) { gpg_error_t err; kbnode_t keyblock = NULL; KEYDB_HANDLE kdbhd = NULL; int modified = 0; PKT_public_key *pk; kbnode_t node; strlist_t sl; int any; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* We require a fingerprint because only this uniquely identifies a key and may thus be used to select a key for unattended key signing. */ if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd)) goto leave; if (fix_keyblock (ctrl, &keyblock)) modified++; /* Give some info in verbose. */ if (opt.verbose) { show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 1/*with_revoker*/, 1/*with_fingerprint*/, 0, 0, 1); es_fflush (es_stdout); } pk = keyblock->pkt->pkt.public_key; if (pk->flags.revoked) { if (!opt.verbose) show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); log_error ("%s%s", _("Key is revoked."), _(" Unable to sign.\n")); goto leave; } /* Set the flags according to the UIDS list. Fixme: We may want to use classify_user_id along with dedicated compare functions so that we match the same way as in the key lookup. */ any = 0; menu_select_uid (keyblock, 0); /* Better clear the flags first. */ for (sl=uids; sl; sl = sl->next) { const char *name = sl->d; int count = 0; sl->flags &= ~(1|2); /* Clear flags used for error reporting. */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; if (uid->attrib_data) ; else if (*name == '=' && strlen (name+1) == uid->len && !memcmp (uid->name, name + 1, uid->len)) { /* Exact match - we don't do a check for ambiguity * in this case. */ node->flag |= NODFLG_SELUID; if (any != -1) { sl->flags |= 1; /* Report as found. */ any = 1; } } else if (ascii_memistr (uid->name, uid->len, *name == '*'? name+1:name)) { node->flag |= NODFLG_SELUID; if (any != -1) { sl->flags |= 1; /* Report as found. */ any = 1; } count++; } } } if (count > 1) { any = -1; /* Force failure at end. */ sl->flags |= 2; /* Report as ambiguous. */ } } /* Check whether all given user ids were found. */ for (sl=uids; sl; sl = sl->next) if (!(sl->flags & 1)) any = -1; /* That user id was not found. */ /* Print an error if there was a problem with the user ids. */ if (uids && any < 1) { if (!opt.verbose) show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); es_fflush (es_stdout); for (sl=uids; sl; sl = sl->next) { if ((sl->flags & 2)) log_info (_("Invalid user ID '%s': %s\n"), sl->d, gpg_strerror (GPG_ERR_AMBIGUOUS_NAME)); else if (!(sl->flags & 1)) log_info (_("Invalid user ID '%s': %s\n"), sl->d, gpg_strerror (GPG_ERR_NOT_FOUND)); } log_error ("%s %s", _("No matching user IDs."), _("Nothing to sign.\n")); goto leave; } /* Sign. */ sign_uids (ctrl, es_stdout, keyblock, locusr, &modified, local, 0, 0, 0, 1); es_fflush (es_stdout); if (modified) { err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } } else log_info (_("Key not changed so no update needed.\n")); if (update_trust) revalidation_mark (ctrl); leave: release_kbnode (keyblock); keydb_release (kdbhd); } /* Unattended revocation of a key signatures. USERNAME specifies the * key; this should best be a fingerprint. SIGTOREV is the user-id of * the key for which the key signature shall be removed. Only * non-self-signatures can be removed with this functions. If * AFFECTED_UIDS is not NULL only the key signatures on these user-ids * are revoked. */ void keyedit_quick_revsig (ctrl_t ctrl, const char *username, const char *sigtorev, strlist_t affected_uids) { gpg_error_t err; int no_signing_key = 0; KEYDB_HANDLE kdbhd = NULL; kbnode_t keyblock = NULL; PKT_public_key *primarypk; /* Points into KEYBLOCK. */ u32 *primarykid; PKT_public_key *pksigtorev = NULL; u32 *pksigtorevkid; kbnode_t node, n; int skip_remaining; int consider_sig; strlist_t sl; struct sign_attrib attrib = { 0 }; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* Search the key; we don't want the whole getkey stuff here. Noet * that we are looking for the public key here. */ err = quick_find_keyblock (ctrl, username, 0, &kdbhd, &keyblock); if (err) goto leave; log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY || keyblock->pkt->pkttype == PKT_SECRET_KEY); primarypk = keyblock->pkt->pkt.public_key; primarykid = pk_keyid (primarypk); /* Get the signing key we want to revoke. This must be one of our * signing keys. We will compare only the keyid because we don't * assume that we have duplicated keyids on our own secret keys. If * a there is a duplicated one we will notice this when creating the * revocation. */ pksigtorev = xtrycalloc (1, sizeof *pksigtorev); if (!pksigtorev) { err = gpg_error_from_syserror (); goto leave; } pksigtorev->req_usage = PUBKEY_USAGE_CERT; err = getkey_byname (ctrl, NULL, pksigtorev, sigtorev, 1, NULL); if (err) { no_signing_key = 1; goto leave; } pksigtorevkid = pk_keyid (pksigtorev); /* Find the signatures we want to revoke and set a mark. */ skip_remaining = consider_sig = 0; for (node = keyblock; node; node = node->next) { node->flag &= ~NODFLG_MARK_A; if (skip_remaining) ; else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) skip_remaining = 1; else if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; consider_sig = !affected_uids; for (sl = affected_uids; !consider_sig && sl; sl = sl->next) { const char *name = sl->d; if (uid->attrib_data) ; else if (*name == '=' && strlen (name+1) == uid->len && !memcmp (uid->name, name + 1, uid->len)) { /* Exact match. */ consider_sig = 1; } else if (ascii_memistr (uid->name, uid->len, *name == '*'? name+1:name)) { /* Case-insensitive substring match. */ consider_sig = 1; } } } else if (node->pkt->pkttype == PKT_SIGNATURE) { /* We need to sort the signatures so that we can figure out * whether the key signature has been revoked or the * revocation has been superseded by a new key * signature. */ PKT_signature *sig; unsigned int sigcount = 0; kbnode_t *sigarray; /* Allocate an array large enogh for all signatures. */ for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next) sigcount++; sigarray = xtrycalloc (sigcount, sizeof *sigarray); if (!sigarray) { err = gpg_error_from_syserror (); goto leave; } /* Now fill the array with signatures we are interested in. * We also move NODE forward to the end. */ sigcount = 0; for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; node=n, n=n->next) { sig = n->pkt->pkt.signature; if (!keyid_cmp (primarykid, sig->keyid)) continue; /* Ignore self-signatures. */ if (keyid_cmp (pksigtorevkid, sig->keyid)) continue; /* Ignore non-matching signatures. */ n->flag &= ~NODFLG_MARK_B; /* Clear flag used by cm_signode. */ sigarray[sigcount++] = n; } if (sigcount) { qsort (sigarray, sigcount, sizeof *sigarray, cmp_signodes); /* log_debug ("Sorted signatures:\n"); */ /* for (idx=0; idx < sigcount; idx++) */ /* { */ /* sig = sigarray[idx]->pkt->pkt.signature; */ /* log_debug ("%s 0x%02x %s\n", keystr (sig->keyid), */ /* sig->sig_class, datestr_from_sig (sig)); */ /* } */ sig = sigarray[sigcount-1]->pkt->pkt.signature; if ((consider_sig || !affected_uids) && IS_UID_REV (sig)) { if (!opt.quiet) log_info ("sig by %s already revoked at %s\n", keystr (sig->keyid), datestr_from_sig (sig)); } else if ((consider_sig && IS_UID_SIG (sig)) || (!affected_uids && IS_KEY_SIG (sig))) node->flag |= NODFLG_MARK_A; /* Select signature. */ } xfree (sigarray); } } /* Check whether any signatures were done by the given key. We do * not return an error if none were found. */ for (node = keyblock; node; node = node->next) if ((node->flag & NODFLG_MARK_A)) break; if (!node) { if (opt.verbose) log_info (_("Not signed by you.\n")); err = 0; goto leave; } /* Revoke all marked signatures. */ attrib.reason = get_default_sig_revocation_reason (); reloop: /* (we must repeat because we are modifying the list) */ for (node = keyblock; node; node = node->next) { kbnode_t unode; PKT_signature *sig; PACKET *pkt; if (!(node->flag & NODFLG_MARK_A)) continue; node->flag &= ~NODFLG_MARK_A; if (IS_KEY_SIG (node->pkt->pkt.signature)) unode = NULL; else { unode = find_prev_kbnode (keyblock, node, PKT_USER_ID); log_assert (unode); } attrib.non_exportable = !node->pkt->pkt.signature->flags.exportable; err = make_keysig_packet (ctrl, &sig, primarypk, unode? unode->pkt->pkt.user_id : NULL, NULL, pksigtorev, 0x30, 0, 0, sign_mk_attrib, &attrib, NULL); if (err) { log_error ("signing failed: %s\n", gpg_strerror (err)); goto leave; } pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; if (unode) insert_kbnode (unode, new_kbnode (pkt), 0); goto reloop; } err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } revalidation_mark (ctrl); leave: if (err) { log_error (_("revoking the key signature failed: %s\n"), gpg_strerror (err)); if (no_signing_key) print_further_info ("error getting key used to make the key signature"); write_status_error ("keyedit.revoke.sig", err); } release_revocation_reason_info (attrib.reason); free_public_key (pksigtorev); release_kbnode (keyblock); keydb_release (kdbhd); } /* Unattended subkey creation function. * */ void keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr, const char *usagestr, const char *expirestr) { gpg_error_t err; kbnode_t keyblock; KEYDB_HANDLE kdbhd; int modified = 0; PKT_public_key *pk; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* We require a fingerprint because only this uniquely identifies a * key and may thus be used to select a key for unattended subkey * creation. */ if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd)) goto leave; if (fix_keyblock (ctrl, &keyblock)) modified++; pk = keyblock->pkt->pkt.public_key; if (pk->flags.revoked) { if (!opt.verbose) show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); log_error ("%s%s", _("Key is revoked."), "\n"); goto leave; } /* Create the subkey. Note that the called function already prints * an error message. */ if (!generate_subkeypair (ctrl, keyblock, algostr, usagestr, expirestr)) modified = 1; es_fflush (es_stdout); /* Store. */ if (modified) { err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } } else log_info (_("Key not changed so no update needed.\n")); leave: release_kbnode (keyblock); keydb_release (kdbhd); } /* Unattended expiration setting function for the main key. If * SUBKEYFPRS is not NULL and SUBKEYSFPRS[0] is neither NULL, it is * expected to be an array of fingerprints for subkeys to change. It * may also be an array which just one item "*" to indicate that all * keys shall be set to that expiration date. */ void keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr, char **subkeyfprs) { gpg_error_t err; kbnode_t keyblock, node; KEYDB_HANDLE kdbhd; int modified = 0; PKT_public_key *pk; u32 expire; int primary_only = 0; int idx; #ifdef HAVE_W32_SYSTEM /* See keyedit_menu for why we need this. */ check_trustdb_stale (ctrl); #endif /* We require a fingerprint because only this uniquely identifies a * key and may thus be used to select a key for unattended * expiration setting. */ err = find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd); if (err) goto leave; if (fix_keyblock (ctrl, &keyblock)) modified++; pk = keyblock->pkt->pkt.public_key; if (pk->flags.revoked) { if (!opt.verbose) show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1); log_error ("%s%s", _("Key is revoked."), "\n"); err = gpg_error (GPG_ERR_CERT_REVOKED); goto leave; } expire = parse_expire_string (expirestr); if (expire == (u32)-1 ) { log_error (_("'%s' is not a valid expiration time\n"), expirestr); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } if (expire) expire += make_timestamp (); /* Check whether a subkey's expiration time shall be changed or the * expiration time of all keys. */ if (!subkeyfprs || !subkeyfprs[0]) primary_only = 1; else if ( !strcmp (subkeyfprs[0], "*") && !subkeyfprs[1]) { /* Change all subkeys keys which have not been revoked and are * not yet expired. */ merge_keys_and_selfsig (ctrl, keyblock); for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY && (pk = node->pkt->pkt.public_key) && !pk->flags.revoked && !pk->has_expired) node->flag |= NODFLG_SELKEY; } } else { /* Change specified subkeys. */ KEYDB_SEARCH_DESC desc; byte fprbin[MAX_FINGERPRINT_LEN]; size_t fprlen; err = 0; merge_keys_and_selfsig (ctrl, keyblock); for (idx=0; subkeyfprs[idx]; idx++) { int any = 0; /* Parse the fingerprint. */ if (classify_user_id (subkeyfprs[idx], &desc, 1) || desc.mode != KEYDB_SEARCH_MODE_FPR) { log_error (_("\"%s\" is not a proper fingerprint\n"), subkeyfprs[idx] ); if (!err) err = gpg_error (GPG_ERR_INV_NAME); continue; } /* Set the flag for the matching non revoked subkey. */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY && (pk = node->pkt->pkt.public_key) && !pk->flags.revoked ) { fingerprint_from_pk (pk, fprbin, &fprlen); if (fprlen == 20 && !memcmp (fprbin, desc.u.fpr, 20)) { node->flag |= NODFLG_SELKEY; any = 1; } } } if (!any) { log_error (_("subkey \"%s\" not found\n"), subkeyfprs[idx]); if (!err) err = gpg_error (GPG_ERR_NOT_FOUND); } } if (err) goto leave; } /* Set the new expiration date. */ err = menu_expire (ctrl, keyblock, primary_only? 1 : 2, expire); if (gpg_err_code (err) == GPG_ERR_TRUE) modified = 1; else if (err) goto leave; es_fflush (es_stdout); /* Store. */ if (modified) { err = keydb_update_keyblock (ctrl, kdbhd, keyblock); if (err) { log_error (_("update failed: %s\n"), gpg_strerror (err)); goto leave; } if (update_trust) revalidation_mark (ctrl); } else log_info (_("Key not changed so no update needed.\n")); leave: release_kbnode (keyblock); keydb_release (kdbhd); if (err) write_status_error ("set_expire", err); } static void tty_print_notations (int indent, PKT_signature * sig) { int first = 1; struct notation *notation, *nd; if (indent < 0) { first = 0; indent = -indent; } notation = sig_to_notation (sig); for (nd = notation; nd; nd = nd->next) { if (!first) tty_printf ("%*s", indent, ""); else first = 0; tty_print_utf8_string (nd->name, strlen (nd->name)); tty_printf ("="); tty_print_utf8_string (nd->value, strlen (nd->value)); tty_printf ("\n"); } free_notation (notation); } /* * Show preferences of a public keyblock. */ static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig, int verbose) { const prefitem_t fake = { 0, 0 }; const prefitem_t *prefs; int i; if (!uid) return; if (uid->prefs) prefs = uid->prefs; else if (verbose) prefs = &fake; else return; if (verbose) { int any, des_seen = 0, sha1_seen = 0, uncomp_seen = 0; tty_printf (" "); tty_printf (_("Cipher: ")); for (i = any = 0; prefs[i].type; i++) { if (prefs[i].type == PREFTYPE_SYM) { if (any) tty_printf (", "); any = 1; /* We don't want to display strings for experimental algos */ if (!openpgp_cipher_test_algo (prefs[i].value) && prefs[i].value < 100) tty_printf ("%s", openpgp_cipher_algo_name (prefs[i].value)); else tty_printf ("[%d]", prefs[i].value); if (prefs[i].value == CIPHER_ALGO_3DES) des_seen = 1; } } if (!des_seen) { if (any) tty_printf (", "); tty_printf ("%s", openpgp_cipher_algo_name (CIPHER_ALGO_3DES)); } tty_printf ("\n "); tty_printf (_("AEAD: ")); for (i = any = 0; prefs[i].type; i++) { if (prefs[i].type == PREFTYPE_AEAD) { if (any) tty_printf (", "); any = 1; /* We don't want to display strings for experimental algos */ if (!openpgp_aead_test_algo (prefs[i].value) && prefs[i].value < 100) tty_printf ("%s", openpgp_aead_algo_name (prefs[i].value)); else tty_printf ("[%d]", prefs[i].value); } } tty_printf ("\n "); tty_printf (_("Digest: ")); for (i = any = 0; prefs[i].type; i++) { if (prefs[i].type == PREFTYPE_HASH) { if (any) tty_printf (", "); any = 1; /* We don't want to display strings for experimental algos */ if (!gcry_md_test_algo (prefs[i].value) && prefs[i].value < 100) tty_printf ("%s", gcry_md_algo_name (prefs[i].value)); else tty_printf ("[%d]", prefs[i].value); if (prefs[i].value == DIGEST_ALGO_SHA1) sha1_seen = 1; } } if (!sha1_seen) { if (any) tty_printf (", "); tty_printf ("%s", gcry_md_algo_name (DIGEST_ALGO_SHA1)); } tty_printf ("\n "); tty_printf (_("Compression: ")); for (i = any = 0; prefs[i].type; i++) { if (prefs[i].type == PREFTYPE_ZIP) { const char *s = compress_algo_to_string (prefs[i].value); if (any) tty_printf (", "); any = 1; /* We don't want to display strings for experimental algos */ if (s && prefs[i].value < 100) tty_printf ("%s", s); else tty_printf ("[%d]", prefs[i].value); if (prefs[i].value == COMPRESS_ALGO_NONE) uncomp_seen = 1; } } if (!uncomp_seen) { if (any) tty_printf (", "); else { tty_printf ("%s", compress_algo_to_string (COMPRESS_ALGO_ZIP)); tty_printf (", "); } tty_printf ("%s", compress_algo_to_string (COMPRESS_ALGO_NONE)); } if (uid->flags.mdc || uid->flags.aead || !uid->flags.ks_modify) { tty_printf ("\n "); tty_printf (_("Features: ")); any = 0; if (uid->flags.mdc) { tty_printf ("MDC"); any = 1; } if (uid->flags.aead) { if (any) tty_printf (", "); tty_printf ("AEAD"); } if (!uid->flags.ks_modify) { if (any) tty_printf (", "); tty_printf (_("Keyserver no-modify")); } } tty_printf ("\n"); if (selfsig) { const byte *pref_ks; size_t pref_ks_len; pref_ks = parse_sig_subpkt (selfsig, 1, SIGSUBPKT_PREF_KS, &pref_ks_len); if (pref_ks && pref_ks_len) { tty_printf (" "); tty_printf (_("Preferred keyserver: ")); tty_print_utf8_string (pref_ks, pref_ks_len); tty_printf ("\n"); } if (selfsig->flags.notation) { tty_printf (" "); tty_printf (_("Notations: ")); tty_print_notations (5 + strlen (_("Notations: ")), selfsig); } } } else { tty_printf (" "); for (i = 0; prefs[i].type; i++) { tty_printf (" %c%d", prefs[i].type == PREFTYPE_SYM ? 'S' : prefs[i].type == PREFTYPE_AEAD ? 'A' : prefs[i].type == PREFTYPE_HASH ? 'H' : prefs[i].type == PREFTYPE_ZIP ? 'Z' : '?', prefs[i].value); } if (uid->flags.mdc) tty_printf (" [mdc]"); if (uid->flags.aead) tty_printf (" [aead]"); if (!uid->flags.ks_modify) tty_printf (" [no-ks-modify]"); tty_printf ("\n"); } } /* This is the version of show_key_with_all_names used when opt.with_colons is used. It prints all available data in a easy to parse format and does not translate utf8 */ static void show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock) { KBNODE node; int i, j, ulti_hack = 0; byte pk_version = 0; PKT_public_key *primary = NULL; int have_seckey; if (!fp) fp = es_stdout; /* the keys */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)) { PKT_public_key *pk = node->pkt->pkt.public_key; u32 keyid[2]; if (node->pkt->pkttype == PKT_PUBLIC_KEY) { pk_version = pk->version; primary = pk; } keyid_from_pk (pk, keyid); have_seckey = agent_probe_secret_key (ctrl, pk); if (node->pkt->pkttype == PKT_PUBLIC_KEY) es_fputs (have_seckey? "sec:" : "pub:", fp); else es_fputs (have_seckey? "ssb:" : "sub:", fp); if (!pk->flags.valid) es_putc ('i', fp); else if (pk->flags.revoked) es_putc ('r', fp); else if (pk->has_expired) es_putc ('e', fp); else if (!(opt.fast_list_mode || opt.no_expensive_trust_checks)) { int trust = get_validity_info (ctrl, keyblock, pk, NULL); if (trust == 'u') ulti_hack = 1; es_putc (trust, fp); } es_fprintf (fp, ":%u:%d:%08lX%08lX:%lu:%lu::", nbits_from_pk (pk), pk->pubkey_algo, (ulong) keyid[0], (ulong) keyid[1], (ulong) pk->timestamp, (ulong) pk->expiredate); if (node->pkt->pkttype == PKT_PUBLIC_KEY && !(opt.fast_list_mode || opt.no_expensive_trust_checks)) es_putc (get_ownertrust_info (ctrl, pk, 0), fp); es_putc (':', fp); es_putc (':', fp); es_putc (':', fp); /* Print capabilities. */ if ((pk->pubkey_usage & PUBKEY_USAGE_ENC)) es_putc ('e', fp); if ((pk->pubkey_usage & PUBKEY_USAGE_SIG)) es_putc ('s', fp); if ((pk->pubkey_usage & PUBKEY_USAGE_CERT)) es_putc ('c', fp); if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH)) es_putc ('a', fp); if ((pk->pubkey_usage & PUBKEY_USAGE_RENC)) es_putc ('r', fp); if ((pk->pubkey_usage & PUBKEY_USAGE_TIME)) es_putc ('t', fp); if ((pk->pubkey_usage & PUBKEY_USAGE_GROUP)) es_putc ('g', fp); es_putc ('\n', fp); print_fingerprint (ctrl, fp, pk, 0); print_revokers (fp, pk); } } /* the user ids */ i = 0; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; ++i; if (uid->attrib_data) es_fputs ("uat:", fp); else es_fputs ("uid:", fp); if (uid->flags.revoked) es_fputs ("r::::::::", fp); else if (uid->flags.expired) es_fputs ("e::::::::", fp); else if (opt.fast_list_mode || opt.no_expensive_trust_checks) es_fputs ("::::::::", fp); else { int uid_validity; if (primary && !ulti_hack) uid_validity = get_validity_info (ctrl, keyblock, primary, uid); else uid_validity = 'u'; es_fprintf (fp, "%c::::::::", uid_validity); } if (uid->attrib_data) es_fprintf (fp, "%u %lu", uid->numattribs, uid->attrib_len); else es_write_sanitized (fp, uid->name, uid->len, ":", NULL); es_putc (':', fp); /* signature class */ es_putc (':', fp); /* capabilities */ es_putc (':', fp); /* preferences */ if (pk_version > 3 || uid->selfsigversion > 3) { const prefitem_t *prefs = uid->prefs; for (j = 0; prefs && prefs[j].type; j++) { if (j) es_putc (' ', fp); es_fprintf (fp, "%c%d", prefs[j].type == PREFTYPE_SYM ? 'S' : prefs[j].type == PREFTYPE_HASH ? 'H' : prefs[j].type == PREFTYPE_ZIP ? 'Z' : '?', prefs[j].value); } if (uid->flags.mdc) es_fputs (",mdc", fp); if (uid->flags.aead) es_fputs (",aead", fp); if (!uid->flags.ks_modify) es_fputs (",no-ks-modify", fp); } es_putc (':', fp); /* flags */ es_fprintf (fp, "%d,", i); if (uid->flags.primary) es_putc ('p', fp); if (uid->flags.revoked) es_putc ('r', fp); if (uid->flags.expired) es_putc ('e', fp); if ((node->flag & NODFLG_SELUID)) es_putc ('s', fp); if ((node->flag & NODFLG_MARK_A)) es_putc ('m', fp); es_putc (':', fp); if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP) { #ifdef USE_TOFU enum tofu_policy policy; if (! tofu_get_policy (ctrl, primary, uid, &policy) && policy != TOFU_POLICY_NONE) es_fprintf (fp, "%s", tofu_policy_str (policy)); #endif /*USE_TOFU*/ } es_putc (':', fp); es_putc ('\n', fp); } } } static void show_names (ctrl_t ctrl, estream_t fp, kbnode_t keyblock, PKT_public_key * pk, unsigned int flag, int with_prefs) { KBNODE node; int i = 0; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && !is_deleted_kbnode (node)) { PKT_user_id *uid = node->pkt->pkt.user_id; ++i; if (!flag || (flag && (node->flag & flag))) { if (!(flag & NODFLG_MARK_A) && pk) tty_fprintf (fp, "%s ", uid_trust_string_fixed (ctrl, pk, uid)); if (flag & NODFLG_MARK_A) tty_fprintf (fp, " "); else if (node->flag & NODFLG_SELUID) tty_fprintf (fp, "(%d)* ", i); else if (uid->flags.primary) tty_fprintf (fp, "(%d). ", i); else tty_fprintf (fp, "(%d) ", i); tty_print_utf8_string2 (fp, uid->name, uid->len, 0); tty_fprintf (fp, "\n"); if (with_prefs && pk) { if (pk->version > 3 || uid->selfsigversion > 3) { PKT_signature *selfsig = NULL; KBNODE signode; for (signode = node->next; signode && signode->pkt->pkttype == PKT_SIGNATURE; signode = signode->next) { if (signode->pkt->pkt.signature-> flags.chosen_selfsig) { selfsig = signode->pkt->pkt.signature; break; } } show_prefs (uid, selfsig, with_prefs == 2); } else tty_fprintf (fp, _("There are no preferences on a" " PGP 2.x-style user ID.\n")); } } } } } /* * Display the key a the user ids, if only_marked is true, do only so * for user ids with mark A flag set and do not display the index * number. If FP is not NULL print to the given stream and not to the * tty (ignored in with-colons mode). */ static void show_key_with_all_names (ctrl_t ctrl, estream_t fp, KBNODE keyblock, int only_marked, int with_revoker, int with_fpr, int with_subkeys, int with_prefs, int nowarn) { gpg_error_t err; kbnode_t node; int i; int do_warn = 0; int have_seckey = 0; char *serialno = NULL; PKT_public_key *primary = NULL; char pkstrbuf[PUBKEY_STRING_SIZE]; if (opt.with_colons) { show_key_with_all_names_colon (ctrl, fp, keyblock); return; } /* the keys */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || (with_subkeys && node->pkt->pkttype == PKT_PUBLIC_SUBKEY && !is_deleted_kbnode (node))) { PKT_public_key *pk = node->pkt->pkt.public_key; const char *otrust = "err"; const char *trust = "err"; if (node->pkt->pkttype == PKT_PUBLIC_KEY) { /* do it here, so that debug messages don't clutter the * output */ static int did_warn = 0; trust = get_validity_string (ctrl, pk, NULL); otrust = get_ownertrust_string (ctrl, pk, 0); /* Show a warning once */ if (!did_warn && (get_validity (ctrl, keyblock, pk, NULL, NULL, 0) & TRUST_FLAG_PENDING_CHECK)) { did_warn = 1; do_warn = 1; } primary = pk; } if (pk->flags.revoked) { char *user = get_user_id_string_native (ctrl, pk->revoked.keyid); tty_fprintf (fp, _("The following key was revoked on" " %s by %s key %s\n"), revokestr_from_pk (pk), gcry_pk_algo_name (pk->revoked.algo), user); xfree (user); } if (with_revoker) { if (!pk->revkey && pk->numrevkeys) BUG (); else for (i = 0; i < pk->numrevkeys; i++) { u32 r_keyid[2]; char *user; const char *algo; algo = gcry_pk_algo_name (pk->revkey[i].algid); keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, pk->revkey[i].fprlen, r_keyid); user = get_user_id_string_native (ctrl, r_keyid); tty_fprintf (fp, _("This key may be revoked by %s key %s"), algo ? algo : "?", user); if (pk->revkey[i].class & 0x40) { tty_fprintf (fp, " "); tty_fprintf (fp, _("(sensitive)")); } tty_fprintf (fp, "\n"); xfree (user); } } keyid_from_pk (pk, NULL); xfree (serialno); serialno = NULL; { char *hexgrip; err = hexkeygrip_from_pk (pk, &hexgrip); if (err) { log_error ("error computing a keygrip: %s\n", gpg_strerror (err)); have_seckey = 0; } else have_seckey = !agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL); xfree (hexgrip); } tty_fprintf (fp, "%s%c %s/%s", node->pkt->pkttype == PKT_PUBLIC_KEY && have_seckey? "sec" : node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" : have_seckey ? "ssb" : "sub", (node->flag & NODFLG_SELKEY) ? '*' : ' ', pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr (pk->keyid)); if (opt.legacy_list_mode) tty_fprintf (fp, " "); else tty_fprintf (fp, "\n "); tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk)); tty_fprintf (fp, " "); if (pk->flags.revoked) tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk)); else if (pk->has_expired) tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk)); else tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk)); tty_fprintf (fp, " "); tty_fprintf (fp, _("usage: %s"), usagestr_from_pk (pk, 1)); tty_fprintf (fp, "\n"); if (serialno) { /* The agent told us that a secret key is available and that it has been stored on a card. */ tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "", _("card-no: ")); if (strlen (serialno) == 32 && !strncmp (serialno, "D27600012401", 12)) { /* This is an OpenPGP card. Print the relevant part. */ /* Example: D2760001240101010001000003470000 */ /* xxxxyyyyyyyy */ tty_fprintf (fp, "%.*s %.*s\n", 4, serialno+16, 8, serialno+20); } else tty_fprintf (fp, "%s\n", serialno); } else if (pk->seckey_info && pk->seckey_info->is_protected && pk->seckey_info->s2k.mode == 1002) { /* FIXME: Check whether this code path is still used. */ tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "", _("card-no: ")); if (pk->seckey_info->ivlen == 16 && !memcmp (pk->seckey_info->iv, "\xD2\x76\x00\x01\x24\x01", 6)) { /* This is an OpenPGP card. */ for (i = 8; i < 14; i++) { if (i == 10) tty_fprintf (fp, " "); tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]); } } else { /* Unknown card: Print all. */ for (i = 0; i < pk->seckey_info->ivlen; i++) tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]); } tty_fprintf (fp, "\n"); } if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_SECRET_KEY) { if (opt.trust_model != TM_ALWAYS) { tty_fprintf (fp, "%*s", opt.legacy_list_mode? ((int) keystrlen () + 13):5, ""); /* Ownertrust is only meaningful for the PGP or classic trust models, or PGP combined with TOFU */ if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC || opt.trust_model == TM_TOFU_PGP) { int width = 14 - strlen (otrust); if (width <= 0) width = 1; tty_fprintf (fp, _("trust: %s"), otrust); tty_fprintf (fp, "%*s", width, ""); } tty_fprintf (fp, _("validity: %s"), trust); tty_fprintf (fp, "\n"); } if (node->pkt->pkttype == PKT_PUBLIC_KEY && (get_ownertrust (ctrl, pk) & TRUST_FLAG_DISABLED)) { tty_fprintf (fp, "*** "); tty_fprintf (fp, _("This key has been disabled")); tty_fprintf (fp, "\n"); } } if ((node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_SECRET_KEY) && with_fpr) { print_fingerprint (ctrl, fp, pk, 2); tty_fprintf (fp, "\n"); } } } show_names (ctrl, fp, keyblock, primary, only_marked ? NODFLG_MARK_A : 0, with_prefs); if (do_warn && !nowarn) tty_fprintf (fp, _("Please note that the shown key validity" " is not necessarily correct\n" "unless you restart the program.\n")); xfree (serialno); } /* Display basic key information. This function is suitable to show * information on the key without any dependencies on the trustdb or * any other internal GnuPG stuff. KEYBLOCK may either be a public or * a secret key. This function may be called with KEYBLOCK containing * secret keys and thus the printing of "pub" vs. "sec" does only * depend on the packet type and not by checking with gpg-agent. If * PRINT_SEC is set "sec" is printed instead of "pub". */ void show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock, int print_sec) { KBNODE node; int i; char pkstrbuf[PUBKEY_STRING_SIZE]; /* The primary key */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_SECRET_KEY) { PKT_public_key *pk = node->pkt->pkt.public_key; const char *tag; if (node->pkt->pkttype == PKT_SECRET_KEY || print_sec) tag = "sec"; else tag = "pub"; /* Note, we use the same format string as in other show functions to make the translation job easier. */ tty_printf ("%s %s/%s ", tag, pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk (pk)); tty_printf (_("created: %s"), datestr_from_pk (pk)); tty_printf (" "); tty_printf (_("expires: %s"), expirestr_from_pk (pk)); tty_printf ("\n"); print_fingerprint (ctrl, NULL, pk, 3); tty_printf ("\n"); } } /* The user IDs. */ + (void)i; /* Counting User IDs */ for (i = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; ++i; tty_printf (" "); if (uid->flags.revoked) tty_printf ("[%s] ", _("revoked")); else if (uid->flags.expired) tty_printf ("[%s] ", _("expired")); tty_print_utf8_string (uid->name, uid->len); tty_printf ("\n"); } } } static void show_key_and_fingerprint (ctrl_t ctrl, kbnode_t keyblock, int with_subkeys) { kbnode_t node; PKT_public_key *pk = NULL; char pkstrbuf[PUBKEY_STRING_SIZE]; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) { pk = node->pkt->pkt.public_key; tty_printf ("pub %s/%s %s ", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk(pk), datestr_from_pk (pk)); } else if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; tty_print_utf8_string (uid->name, uid->len); break; } } tty_printf ("\n"); if (pk) print_fingerprint (ctrl, NULL, pk, 2); if (with_subkeys) { for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { pk = node->pkt->pkt.public_key; tty_printf ("sub %s/%s %s [%s]\n", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk(pk), datestr_from_pk (pk), usagestr_from_pk (pk, 0)); print_fingerprint (ctrl, NULL, pk, 4); } } } } /* Show a listing of the primary and its subkeys along with their keygrips. */ static void show_key_and_grip (kbnode_t keyblock) { kbnode_t node; PKT_public_key *pk = NULL; char pkstrbuf[PUBKEY_STRING_SIZE]; char *hexgrip; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY || node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { pk = node->pkt->pkt.public_key; tty_printf ("%s %s/%s %s [%s]\n", node->pkt->pkttype == PKT_PUBLIC_KEY? "pub":"sub", pubkey_string (pk, pkstrbuf, sizeof pkstrbuf), keystr_from_pk(pk), datestr_from_pk (pk), usagestr_from_pk (pk, 0)); if (!hexkeygrip_from_pk (pk, &hexgrip)) { tty_printf (" Keygrip: %s\n", hexgrip); xfree (hexgrip); } } } } /* Show a warning if no uids on the key have the primary uid flag set. */ static void no_primary_warning (KBNODE keyblock) { KBNODE node; int have_primary = 0, uid_count = 0; /* TODO: if we ever start behaving differently with a primary or non-primary attribute ID, we will need to check for attributes here as well. */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID && node->pkt->pkt.user_id->attrib_data == NULL) { uid_count++; if (node->pkt->pkt.user_id->flags.primary == 2) { have_primary = 1; break; } } } if (uid_count > 1 && !have_primary) log_info (_ ("WARNING: no user ID has been marked as primary. This command" " may\n cause a different user ID to become" " the assumed primary.\n")); } /* Print a warning if the latest encryption subkey expires soon. This function is called after the expire data of the primary key has been changed. */ static void subkey_expire_warning (kbnode_t keyblock) { u32 curtime = make_timestamp (); kbnode_t node; PKT_public_key *pk; /* u32 mainexpire = 0; */ u32 subexpire = 0; u32 latest_date = 0; for (node = keyblock; node; node = node->next) { /* if (node->pkt->pkttype == PKT_PUBLIC_KEY) */ /* { */ /* pk = node->pkt->pkt.public_key; */ /* mainexpire = pk->expiredate; */ /* } */ if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY) continue; pk = node->pkt->pkt.public_key; if (!pk->flags.valid) continue; if (pk->flags.revoked) continue; if (pk->timestamp > curtime) continue; /* Ignore future keys. */ if (!(pk->pubkey_usage & PUBKEY_USAGE_ENC)) continue; /* Not an encryption key. */ if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date)) { latest_date = pk->timestamp; subexpire = pk->expiredate; } } if (!subexpire) return; /* No valid subkey with an expiration time. */ if (curtime + (10*86400) > subexpire) { log_info (_("WARNING: Your encryption subkey expires soon.\n")); log_info (_("You may want to change its expiration date too.\n")); } } /* * Ask for a new user id, add the self-signature, and update the * keyblock. If UIDSTRING is not NULL the user ID is generated * unattended using that string. UIDSTRING is expected to be utf-8 * encoded and white space trimmed. Returns true if there is a new * user id. */ static int menu_adduid (ctrl_t ctrl, kbnode_t pub_keyblock, int photo, const char *photo_name, const char *uidstring) { PKT_user_id *uid; PKT_public_key *pk = NULL; PKT_signature *sig = NULL; PACKET *pkt; KBNODE node; KBNODE pub_where = NULL; gpg_error_t err; if (photo && uidstring) return 0; /* Not allowed. */ for (node = pub_keyblock; node; pub_where = node, node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) pk = node->pkt->pkt.public_key; else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) break; } if (!node) /* No subkey. */ pub_where = NULL; log_assert (pk); if (photo) { int hasattrib = 0; for (node = pub_keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && node->pkt->pkt.user_id->attrib_data != NULL) { hasattrib = 1; break; } /* It is legal but bad for compatibility to add a photo ID to a v3 key as it means that PGP2 will not be able to use that key anymore. Also, PGP may not expect a photo on a v3 key. Don't bother to ask this if the key already has a photo - any damage has already been done at that point. -dms */ if (pk->version == 3 && !hasattrib) { if (opt.expert) { tty_printf (_("WARNING: This is a PGP2-style key. " "Adding a photo ID may cause some versions\n" " of PGP to reject this key.\n")); if (!cpr_get_answer_is_yes ("keyedit.v3_photo.okay", _("Are you sure you still want " "to add it? (y/N) "))) return 0; } else { tty_printf (_("You may not add a photo ID to " "a PGP2-style key.\n")); return 0; } } uid = generate_photo_id (ctrl, pk, photo_name); } else uid = generate_user_id (pub_keyblock, uidstring); if (!uid) { if (uidstring) { write_status_error ("adduid", gpg_error (304)); log_error ("%s\n", _("Such a user ID already exists on this key!")); } return 0; } err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x13, 0, 0, keygen_add_std_prefs, pk, NULL); if (err) { write_status_error ("keysig", err); log_error ("signing failed: %s\n", gpg_strerror (err)); free_user_id (uid); return 0; } /* Insert/append to public keyblock */ pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_USER_ID; pkt->pkt.user_id = uid; node = new_kbnode (pkt); if (pub_where) insert_kbnode (pub_where, node, 0); else add_kbnode (pub_keyblock, node); pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; if (pub_where) insert_kbnode (node, new_kbnode (pkt), 0); else add_kbnode (pub_keyblock, new_kbnode (pkt)); return 1; } /* * Remove all selected userids from the keyring */ static void menu_deluid (KBNODE pub_keyblock) { KBNODE node; int selected = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { selected = node->flag & NODFLG_SELUID; if (selected) { /* Only cause a trust update if we delete a non-revoked user id */ if (!node->pkt->pkt.user_id->flags.revoked) update_trust = 1; delete_kbnode (node); } } else if (selected && node->pkt->pkttype == PKT_SIGNATURE) delete_kbnode (node); else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) selected = 0; } commit_kbnode (&pub_keyblock); } static int menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock) { KBNODE node; PKT_user_id *uid = NULL; int changed = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { uid = (node->flag & NODFLG_SELUID) ? node->pkt->pkt.user_id : NULL; } else if (uid && node->pkt->pkttype == PKT_SIGNATURE) { int okay, valid, selfsig, inv_sig, no_key, other_err; tty_printf ("uid "); tty_print_utf8_string (uid->name, uid->len); tty_printf ("\n"); okay = inv_sig = no_key = other_err = 0; if (opt.with_colons) valid = print_and_check_one_sig_colon (ctrl, pub_keyblock, node, &inv_sig, &no_key, &other_err, &selfsig, 1); else valid = print_and_check_one_sig (ctrl, pub_keyblock, node, &inv_sig, &no_key, &other_err, &selfsig, 1, 0); if (valid) { okay = cpr_get_answer_yes_no_quit ("keyedit.delsig.valid", _("Delete this good signature? (y/N/q)")); /* Only update trust if we delete a good signature. The other two cases do not affect trust. */ if (okay) update_trust = 1; } else if (inv_sig || other_err) okay = cpr_get_answer_yes_no_quit ("keyedit.delsig.invalid", _("Delete this invalid signature? (y/N/q)")); else if (no_key) okay = cpr_get_answer_yes_no_quit ("keyedit.delsig.unknown", _("Delete this unknown signature? (y/N/q)")); if (okay == -1) break; if (okay && selfsig && !cpr_get_answer_is_yes ("keyedit.delsig.selfsig", _("Really delete this self-signature? (y/N)"))) okay = 0; if (okay) { delete_kbnode (node); changed++; } } else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) uid = NULL; } if (changed) { commit_kbnode (&pub_keyblock); tty_printf (ngettext("Deleted %d signature.\n", "Deleted %d signatures.\n", changed), changed); } else tty_printf (_("Nothing deleted.\n")); return changed; } static int menu_clean (ctrl_t ctrl, kbnode_t keyblock, int self_only) { KBNODE uidnode; int modified = 0, select_all = !count_selected_uids (keyblock); for (uidnode = keyblock->next; uidnode && uidnode->pkt->pkttype != PKT_PUBLIC_SUBKEY; uidnode = uidnode->next) { if (uidnode->pkt->pkttype == PKT_USER_ID && (uidnode->flag & NODFLG_SELUID || select_all)) { int uids = 0, sigs = 0; char *user = utf8_to_native (uidnode->pkt->pkt.user_id->name, uidnode->pkt->pkt.user_id->len, 0); clean_one_uid (ctrl, keyblock, uidnode, opt.verbose, self_only, &uids, &sigs); if (uids) { const char *reason; if (uidnode->pkt->pkt.user_id->flags.revoked) reason = _("revoked"); else if (uidnode->pkt->pkt.user_id->flags.expired) reason = _("expired"); else reason = _("invalid"); tty_printf (_("User ID \"%s\" compacted: %s\n"), user, reason); modified = 1; } else if (sigs) { tty_printf (ngettext("User ID \"%s\": %d signature removed\n", "User ID \"%s\": %d signatures removed\n", sigs), user, sigs); modified = 1; } else { tty_printf (self_only == 1 ? _("User ID \"%s\": already minimized\n") : _("User ID \"%s\": already clean\n"), user); } xfree (user); } } return modified; } /* * Remove some of the secondary keys */ static void menu_delkey (KBNODE pub_keyblock) { KBNODE node; int selected = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { selected = node->flag & NODFLG_SELKEY; if (selected) delete_kbnode (node); } else if (selected && node->pkt->pkttype == PKT_SIGNATURE) delete_kbnode (node); else selected = 0; } commit_kbnode (&pub_keyblock); /* No need to set update_trust here since signing keys are no longer used to certify other keys, so there is no change in trust when revoking/removing them. */ } /* * Ask for a new revoker, create the self-signature and put it into * the keyblock. Returns true if there is a new revoker. */ static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive) { PKT_public_key *pk = NULL; PKT_public_key *revoker_pk = NULL; PKT_signature *sig = NULL; PACKET *pkt; struct revocation_key revkey; size_t fprlen; int rc; log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY); pk = pub_keyblock->pkt->pkt.public_key; if (pk->numrevkeys == 0 && pk->version == 3) { /* It is legal but bad for compatibility to add a revoker to a v3 key as it means that PGP2 will not be able to use that key anymore. Also, PGP may not expect a revoker on a v3 key. Don't bother to ask this if the key already has a revoker - any damage has already been done at that point. -dms */ if (opt.expert) { tty_printf (_("WARNING: This is a PGP 2.x-style key. " "Adding a designated revoker may cause\n" " some versions of PGP to reject this key.\n")); if (!cpr_get_answer_is_yes ("keyedit.v3_revoker.okay", _("Are you sure you still want " "to add it? (y/N) "))) return 0; } else { tty_printf (_("You may not add a designated revoker to " "a PGP 2.x-style key.\n")); return 0; } } for (;;) { char *answer; free_public_key (revoker_pk); revoker_pk = xmalloc_clear (sizeof (*revoker_pk)); tty_printf ("\n"); answer = cpr_get_utf8 ("keyedit.add_revoker", _("Enter the user ID of the designated revoker: ")); if (answer[0] == '\0' || answer[0] == CONTROL_D) { xfree (answer); goto fail; } /* Note that I'm requesting CERT here, which usually implies primary keys only, but some casual testing shows that PGP and GnuPG both can handle a designated revocation from a subkey. */ revoker_pk->req_usage = PUBKEY_USAGE_CERT; rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL, NULL, revoker_pk, answer, NULL, NULL, 1); if (rc) { log_error (_("key \"%s\" not found: %s\n"), answer, gpg_strerror (rc)); xfree (answer); continue; } xfree (answer); fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen); if (fprlen != 20 && fprlen != 32) { log_error (_("cannot appoint a PGP 2.x style key as a " "designated revoker\n")); continue; } revkey.fprlen = fprlen; revkey.class = 0x80; if (sensitive) revkey.class |= 0x40; revkey.algid = revoker_pk->pubkey_algo; if (cmp_public_keys (revoker_pk, pk) == 0) { /* This actually causes no harm (after all, a key that designates itself as a revoker is the same as a regular key), but it's easy enough to check. */ log_error (_("you cannot appoint a key as its own " "designated revoker\n")); continue; } keyid_from_pk (pk, NULL); /* Does this revkey already exist? */ if (!pk->revkey && pk->numrevkeys) BUG (); else { int i; for (i = 0; i < pk->numrevkeys; i++) { if (memcmp (&pk->revkey[i], &revkey, sizeof (struct revocation_key)) == 0) { char buf[50]; log_error (_("this key has already been designated " "as a revoker\n")); format_keyid (pk_keyid (pk), KF_LONG, buf, sizeof (buf)); write_status_text (STATUS_ALREADY_SIGNED, buf); break; } } if (i < pk->numrevkeys) continue; } print_key_info (ctrl, NULL, 0, revoker_pk, 0); print_fingerprint (ctrl, NULL, revoker_pk, 2); tty_printf ("\n"); tty_printf (_("WARNING: appointing a key as a designated revoker " "cannot be undone!\n")); tty_printf ("\n"); if (!cpr_get_answer_is_yes ("keyedit.add_revoker.okay", _("Are you sure you want to appoint this " "key as a designated revoker? (y/N) "))) continue; free_public_key (revoker_pk); revoker_pk = NULL; break; } rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk, 0x1F, 0, 0, keygen_add_revkey, &revkey, NULL); if (rc) { write_status_error ("keysig", rc); log_error ("signing failed: %s\n", gpg_strerror (rc)); goto fail; } /* Insert into public keyblock. */ pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (pub_keyblock, new_kbnode (pkt), PKT_SIGNATURE); return 1; fail: if (sig) free_seckey_enc (sig); free_public_key (revoker_pk); return 0; } /* With FORCE_MAINKEY cleared this function handles the interactive * menu option "expire". With UNATTENDED set to 1 this function only * sets the expiration date of the primary key to NEWEXPIRATION and * avoid all interactivity; with a value of 2 only the flagged subkeys * are set to NEWEXPIRATION. Returns 0 if nothing was done, * GPG_ERR_TRUE if the key was modified, or any other error code. */ static gpg_error_t menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock, int unattended, u32 newexpiration) { int signumber, rc; u32 expiredate; int only_mainkey; /* Set if only the mainkey is to be updated. */ PKT_public_key *main_pk, *sub_pk; PKT_user_id *uid; kbnode_t node; u32 keyid[2]; + (void)signumber; if (unattended) { only_mainkey = (unattended == 1); expiredate = newexpiration; } else { int n1; only_mainkey = 0; n1 = count_selected_keys (pub_keyblock); if (n1 > 1) { if (!cpr_get_answer_is_yes ("keyedit.expire_multiple_subkeys.okay", _("Are you sure you want to change the" " expiration time for multiple subkeys? (y/N) "))) return gpg_error (GPG_ERR_CANCELED);; } else if (n1) tty_printf (_("Changing expiration time for a subkey.\n")); else { tty_printf (_("Changing expiration time for the primary key.\n")); only_mainkey = 1; no_primary_warning (pub_keyblock); } expiredate = ask_expiredate (); } /* Now we can actually change the self-signature(s) */ main_pk = sub_pk = NULL; uid = NULL; signumber = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); main_pk->expiredate = expiredate; } else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { if ((node->flag & NODFLG_SELKEY) && unattended != 1) { /* The flag is set and we do not want to set the * expiration date only for the main key. */ sub_pk = node->pkt->pkt.public_key; sub_pk->expiredate = expiredate; } else sub_pk = NULL; } else if (node->pkt->pkttype == PKT_USER_ID) uid = node->pkt->pkt.user_id; else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE && (only_mainkey || sub_pk)) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && ((only_mainkey && uid && uid->created && (sig->sig_class & ~3) == 0x10) || (!only_mainkey && sig->sig_class == 0x18)) && sig->flags.chosen_selfsig) { /* This is a self-signature which is to be replaced. */ PKT_signature *newsig; PACKET *newpkt; signumber++; if ((only_mainkey && main_pk->version < 4) || (!only_mainkey && sub_pk->version < 4)) { log_info (_("You can't change the expiration date of a v3 key\n")); return gpg_error (GPG_ERR_LEGACY_KEY); } if (only_mainkey) rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, keygen_add_key_expire, main_pk); else rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, NULL, sub_pk, main_pk, keygen_add_key_expire, sub_pk); if (rc) { log_error ("make_keysig_packet failed: %s\n", gpg_strerror (rc)); if (gpg_err_code (rc) == GPG_ERR_TRUE) rc = GPG_ERR_GENERAL; return rc; } /* Replace the packet. */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; sub_pk = NULL; } } } update_trust = 1; return gpg_error (GPG_ERR_TRUE); } /* Change the capability of a selected key. This command should only * be used to rectify badly created keys and as such is not suggested * for general use. */ static int menu_changeusage (ctrl_t ctrl, kbnode_t keyblock) { int n1, rc; int mainkey = 0; PKT_public_key *main_pk, *sub_pk; PKT_user_id *uid; kbnode_t node; u32 keyid[2]; n1 = count_selected_keys (keyblock); if (n1 > 1) { tty_printf (_("You must select exactly one key.\n")); return 0; } else if (n1) tty_printf (_("Changing usage of a subkey.\n")); else { tty_printf (_("Changing usage of the primary key.\n")); mainkey = 1; } /* Now we can actually change the self-signature(s) */ main_pk = sub_pk = NULL; uid = NULL; for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); } else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { if (node->flag & NODFLG_SELKEY) sub_pk = node->pkt->pkt.public_key; else sub_pk = NULL; } else if (node->pkt->pkttype == PKT_USER_ID) uid = node->pkt->pkt.user_id; else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE && (mainkey || sub_pk)) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && ((mainkey && uid && uid->created && (sig->sig_class & ~3) == 0x10) || (!mainkey && sig->sig_class == 0x18)) && sig->flags.chosen_selfsig) { /* This is the self-signature which is to be replaced. */ PKT_signature *newsig; PACKET *newpkt; if ((mainkey && main_pk->version < 4) || (!mainkey && sub_pk->version < 4)) { /* Note: This won't happen because we don't support * v3 keys anymore. */ log_info ("You can't change the capabilities of a v3 key\n"); return 0; } if (mainkey) main_pk->pubkey_usage = ask_key_flags (main_pk->pubkey_algo, 0, main_pk->pubkey_usage); else sub_pk->pubkey_usage = ask_key_flags (sub_pk->pubkey_algo, 1, sub_pk->pubkey_usage); if (mainkey) rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, keygen_add_key_flags, main_pk); else rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, NULL, sub_pk, main_pk, keygen_add_key_flags, sub_pk); if (rc) { log_error ("make_keysig_packet failed: %s\n", gpg_strerror (rc)); return 0; } /* Replace the packet. */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; sub_pk = NULL; break; } } } return 1; } static int menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock) { int rc, modified = 0; PKT_public_key *main_pk; KBNODE node; u32 timestamp; log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY); merge_keys_and_selfsig (ctrl, pub_keyblock); main_pk = pub_keyblock->pkt->pkt.public_key; keyid_from_pk (main_pk, NULL); /* We use the same timestamp for all backsigs so that we don't reveal information about the used machine. */ timestamp = make_timestamp (); for (node = pub_keyblock; node; node = node->next) { PKT_public_key *sub_pk = NULL; KBNODE node2, sig_pk = NULL /*,sig_sk = NULL*/; /* char *passphrase; */ /* Find a signing subkey with no backsig */ if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) { if (node->pkt->pkt.public_key->pubkey_usage & PUBKEY_USAGE_SIG) { if (node->pkt->pkt.public_key->flags.backsig) tty_printf (_ ("signing subkey %s is already cross-certified\n"), keystr_from_pk (node->pkt->pkt.public_key)); else sub_pk = node->pkt->pkt.public_key; } else tty_printf (_("subkey %s does not sign and so does" " not need to be cross-certified\n"), keystr_from_pk (node->pkt->pkt.public_key)); } if (!sub_pk) continue; /* Find the selected selfsig on this subkey */ for (node2 = node->next; node2 && node2->pkt->pkttype == PKT_SIGNATURE; node2 = node2->next) if (node2->pkt->pkt.signature->version >= 4 && node2->pkt->pkt.signature->flags.chosen_selfsig) { sig_pk = node2; break; } if (!sig_pk) continue; /* Find the secret subkey that matches the public subkey */ log_debug ("FIXME: Check whether a secret subkey is available.\n"); /* if (!sub_sk) */ /* { */ /* tty_printf (_("no secret subkey for public subkey %s - ignoring\n"), */ /* keystr_from_pk (sub_pk)); */ /* continue; */ /* } */ /* Now we can get to work. */ rc = make_backsig (ctrl, sig_pk->pkt->pkt.signature, main_pk, sub_pk, sub_pk, timestamp, NULL); if (!rc) { PKT_signature *newsig; PACKET *newpkt; rc = update_keysig_packet (ctrl, &newsig, sig_pk->pkt->pkt.signature, main_pk, NULL, sub_pk, main_pk, NULL, NULL); if (!rc) { /* Put the new sig into place on the pubkey */ newpkt = xmalloc_clear (sizeof (*newpkt)); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (sig_pk->pkt, NULL); xfree (sig_pk->pkt); sig_pk->pkt = newpkt; modified = 1; } else { log_error ("update_keysig_packet failed: %s\n", gpg_strerror (rc)); break; } } else { log_error ("make_backsig failed: %s\n", gpg_strerror (rc)); break; } } return modified; } static int change_primary_uid_cb (PKT_signature * sig, void *opaque) { byte buf[1]; /* first clear all primary uid flags so that we are sure none are * lingering around */ delete_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID); delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PRIMARY_UID); /* if opaque is set,we want to set the primary id */ if (opaque) { buf[0] = 1; build_sig_subpkt (sig, SIGSUBPKT_PRIMARY_UID, buf, 1); } return 0; } /* * Set the primary uid flag for the selected UID. We will also reset * all other primary uid flags. For this to work we have to update * all the signature timestamps. If we would do this with the current * time, we lose quite a lot of information, so we use a kludge to * do this: Just increment the timestamp by one second which is * sufficient to updated a signature during import. */ static int menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock) { PKT_public_key *main_pk; PKT_user_id *uid; KBNODE node; u32 keyid[2]; int selected; int attribute = 0; int modified = 0; if (count_selected_uids (pub_keyblock) != 1) { tty_printf (_("Please select exactly one user ID.\n")); return 0; } main_pk = NULL; uid = NULL; selected = 0; /* Is our selected uid an attribute packet? */ for (node = pub_keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && node->flag & NODFLG_SELUID) attribute = (node->pkt->pkt.user_id->attrib_data != NULL); for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) break; /* No more user ids expected - ready. */ if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); } else if (node->pkt->pkttype == PKT_USER_ID) { uid = node->pkt->pkt.user_id; selected = node->flag & NODFLG_SELUID; } else if (main_pk && uid && node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && (uid && (sig->sig_class & ~3) == 0x10) && attribute == (uid->attrib_data != NULL) && sig->flags.chosen_selfsig) { if (sig->version < 4) { char *user = utf8_to_native (uid->name, strlen (uid->name), 0); log_info (_("skipping v3 self-signature on user ID \"%s\"\n"), user); xfree (user); } else { /* This is a selfsignature which is to be replaced. We can just ignore v3 signatures because they are not able to carry the primary ID flag. We also ignore self-sigs on user IDs that are not of the same type that we are making primary. That is, if we are making a user ID primary, we alter user IDs. If we are making an attribute packet primary, we alter attribute packets. */ /* FIXME: We must make sure that we only have one self-signature per user ID here (not counting revocations) */ PKT_signature *newsig; PACKET *newpkt; const byte *p; int action; /* See whether this signature has the primary UID flag. */ p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PRIMARY_UID, NULL); if (!p) p = parse_sig_subpkt (sig, 0, SIGSUBPKT_PRIMARY_UID, NULL); if (p && *p) /* yes */ action = selected ? 0 : -1; else /* no */ action = selected ? 1 : 0; if (action) { int rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, change_primary_uid_cb, action > 0 ? "x" : NULL); if (rc) { log_error ("update_keysig_packet failed: %s\n", gpg_strerror (rc)); return 0; } /* replace the packet */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; modified = 1; } } } } } return modified; } /* * Set preferences to new values for the selected user IDs */ static int menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock) { PKT_public_key *main_pk; PKT_user_id *uid; KBNODE node; u32 keyid[2]; int selected, select_all; int modified = 0; no_primary_warning (pub_keyblock); select_all = !count_selected_uids (pub_keyblock); /* Now we can actually change the self signature(s) */ main_pk = NULL; uid = NULL; selected = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) break; /* No more user-ids expected - ready. */ if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); } else if (node->pkt->pkttype == PKT_USER_ID) { uid = node->pkt->pkt.user_id; selected = select_all || (node->flag & NODFLG_SELUID); } else if (main_pk && uid && selected && node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && (uid && (sig->sig_class & ~3) == 0x10) && sig->flags.chosen_selfsig) { if (sig->version < 4) { char *user = utf8_to_native (uid->name, strlen (uid->name), 0); log_info (_("skipping v3 self-signature on user ID \"%s\"\n"), user); xfree (user); } else { /* This is a selfsignature which is to be replaced * We have to ignore v3 signatures because they are * not able to carry the preferences. */ PKT_signature *newsig; PACKET *newpkt; int rc; rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, keygen_upd_std_prefs, NULL); if (rc) { log_error ("update_keysig_packet failed: %s\n", gpg_strerror (rc)); return 0; } /* replace the packet */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; modified = 1; } } } } return modified; } static int menu_set_keyserver_url (ctrl_t ctrl, const char *url, kbnode_t pub_keyblock) { PKT_public_key *main_pk; PKT_user_id *uid; KBNODE node; u32 keyid[2]; int selected, select_all; int modified = 0; char *answer, *uri; no_primary_warning (pub_keyblock); if (url) answer = xstrdup (url); else { answer = cpr_get_utf8 ("keyedit.add_keyserver", _("Enter your preferred keyserver URL: ")); if (answer[0] == '\0' || answer[0] == CONTROL_D) { xfree (answer); return 0; } } if (!ascii_strcasecmp (answer, "none")) { xfree (answer); uri = NULL; } else { struct keyserver_spec *keyserver = NULL; /* Sanity check the format */ keyserver = parse_keyserver_uri (answer, 1); xfree (answer); if (!keyserver) { log_info (_("could not parse keyserver URL\n")); return 0; } uri = xstrdup (keyserver->uri); free_keyserver_spec (keyserver); } select_all = !count_selected_uids (pub_keyblock); /* Now we can actually change the self signature(s) */ main_pk = NULL; uid = NULL; selected = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) break; /* ready */ if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); } else if (node->pkt->pkttype == PKT_USER_ID) { uid = node->pkt->pkt.user_id; selected = select_all || (node->flag & NODFLG_SELUID); } else if (main_pk && uid && selected && node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && (uid && (sig->sig_class & ~3) == 0x10) && sig->flags.chosen_selfsig) { char *user = utf8_to_native (uid->name, strlen (uid->name), 0); if (sig->version < 4) log_info (_("skipping v3 self-signature on user ID \"%s\"\n"), user); else { /* This is a selfsignature which is to be replaced * We have to ignore v3 signatures because they are * not able to carry the subpacket. */ PKT_signature *newsig; PACKET *newpkt; int rc; const byte *p; size_t plen; p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_KS, &plen); if (p && plen) { tty_printf ("Current preferred keyserver for user" " ID \"%s\": ", user); tty_print_utf8_string (p, plen); tty_printf ("\n"); if (!cpr_get_answer_is_yes ("keyedit.confirm_keyserver", uri ? _("Are you sure you want to replace it? (y/N) ") : _("Are you sure you want to delete it? (y/N) "))) { xfree (user); continue; } } else if (uri == NULL) { /* There is no current keyserver URL, so there is no point in trying to un-set it. */ xfree (user); continue; } rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, keygen_add_keyserver_url, uri); if (rc) { log_error ("update_keysig_packet failed: %s\n", gpg_strerror (rc)); xfree (uri); xfree (user); return 0; } /* replace the packet */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; modified = 1; } xfree (user); } } } xfree (uri); return modified; } static int menu_set_notation (ctrl_t ctrl, const char *string, KBNODE pub_keyblock) { PKT_public_key *main_pk; PKT_user_id *uid; KBNODE node; u32 keyid[2]; int selected, select_all; int modified = 0; char *answer; struct notation *notation; no_primary_warning (pub_keyblock); if (string) answer = xstrdup (string); else { answer = cpr_get_utf8 ("keyedit.add_notation", _("Enter the notation: ")); if (answer[0] == '\0' || answer[0] == CONTROL_D) { xfree (answer); return 0; } } if (!ascii_strcasecmp (answer, "none") || !ascii_strcasecmp (answer, "-")) notation = NULL; /* Delete them all. */ else { notation = string_to_notation (answer, 0); if (!notation) { xfree (answer); return 0; } } xfree (answer); select_all = !count_selected_uids (pub_keyblock); /* Now we can actually change the self signature(s) */ main_pk = NULL; uid = NULL; selected = 0; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY) break; /* ready */ if (node->pkt->pkttype == PKT_PUBLIC_KEY) { main_pk = node->pkt->pkt.public_key; keyid_from_pk (main_pk, keyid); } else if (node->pkt->pkttype == PKT_USER_ID) { uid = node->pkt->pkt.user_id; selected = select_all || (node->flag & NODFLG_SELUID); } else if (main_pk && uid && selected && node->pkt->pkttype == PKT_SIGNATURE) { PKT_signature *sig = node->pkt->pkt.signature; if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1] && (uid && (sig->sig_class & ~3) == 0x10) && sig->flags.chosen_selfsig) { char *user = utf8_to_native (uid->name, strlen (uid->name), 0); if (sig->version < 4) log_info (_("skipping v3 self-signature on user ID \"%s\"\n"), user); else { PKT_signature *newsig; PACKET *newpkt; int rc, skip = 0, addonly = 1; if (sig->flags.notation) { tty_printf ("Current notations for user ID \"%s\":\n", user); tty_print_notations (-9, sig); } else { tty_printf ("No notations on user ID \"%s\"\n", user); if (notation == NULL) { /* There are no current notations, so there is no point in trying to un-set them. */ continue; } } if (notation) { struct notation *n; int deleting = 0; notation->next = sig_to_notation (sig); for (n = notation->next; n; n = n->next) if (strcmp (n->name, notation->name) == 0) { if (notation->value) { if (strcmp (n->value, notation->value) == 0) { if (notation->flags.ignore) { /* Value match with a delete flag. */ n->flags.ignore = 1; deleting = 1; } else { /* Adding the same notation twice, so don't add it at all. */ skip = 1; tty_printf ("Skipping notation:" " %s=%s\n", notation->name, notation->value); break; } } } else { /* No value, so it means delete. */ n->flags.ignore = 1; deleting = 1; } if (n->flags.ignore) { tty_printf ("Removing notation: %s=%s\n", n->name, n->value); addonly = 0; } } if (!notation->flags.ignore && !skip) tty_printf ("Adding notation: %s=%s\n", notation->name, notation->value); /* We tried to delete, but had no matches. */ if (notation->flags.ignore && !deleting) continue; } else { tty_printf ("Removing all notations\n"); addonly = 0; } if (skip || (!addonly && !cpr_get_answer_is_yes ("keyedit.confirm_notation", _("Proceed? (y/N) ")))) continue; rc = update_keysig_packet (ctrl, &newsig, sig, main_pk, uid, NULL, main_pk, keygen_add_notations, notation); if (rc) { log_error ("update_keysig_packet failed: %s\n", gpg_strerror (rc)); free_notation (notation); xfree (user); return 0; } /* replace the packet */ newpkt = xmalloc_clear (sizeof *newpkt); newpkt->pkttype = PKT_SIGNATURE; newpkt->pkt.signature = newsig; free_packet (node->pkt, NULL); xfree (node->pkt); node->pkt = newpkt; modified = 1; if (notation) { /* Snip off the notation list from the sig */ free_notation (notation->next); notation->next = NULL; } xfree (user); } } } } free_notation (notation); return modified; } /* * Select one user id or remove all selection if IDX is 0 or select * all if IDX is -1. Returns: True if the selection changed. */ static int menu_select_uid (KBNODE keyblock, int idx) { KBNODE node; int i; if (idx == -1) /* Select all. */ { for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID) node->flag |= NODFLG_SELUID; return 1; } else if (idx) /* Toggle. */ { for (i = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) if (++i == idx) break; } if (!node) { tty_printf (_("No user ID with index %d\n"), idx); return 0; } for (i = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { if (++i == idx) { if ((node->flag & NODFLG_SELUID)) node->flag &= ~NODFLG_SELUID; else node->flag |= NODFLG_SELUID; } } } } else /* Unselect all */ { for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID) node->flag &= ~NODFLG_SELUID; } return 1; } /* Search in the keyblock for a uid that matches namehash */ static int menu_select_uid_namehash (KBNODE keyblock, const char *namehash) { byte hash[NAMEHASH_LEN]; KBNODE node; int i; log_assert (strlen (namehash) == NAMEHASH_LEN * 2); for (i = 0; i < NAMEHASH_LEN; i++) hash[i] = hextobyte (&namehash[i * 2]); for (node = keyblock->next; node; node = node->next) { if (node->pkt->pkttype == PKT_USER_ID) { namehash_from_uid (node->pkt->pkt.user_id); if (memcmp (node->pkt->pkt.user_id->namehash, hash, NAMEHASH_LEN) == 0) { if (node->flag & NODFLG_SELUID) node->flag &= ~NODFLG_SELUID; else node->flag |= NODFLG_SELUID; break; } } } if (!node) { tty_printf (_("No user ID with hash %s\n"), namehash); return 0; } return 1; } /* * Select secondary keys * Returns: True if the selection changed. */ static int menu_select_key (KBNODE keyblock, int idx, char *p) { KBNODE node; int i, j; int is_hex_digits; is_hex_digits = p && strlen (p) >= 8; if (is_hex_digits) { /* Skip initial spaces. */ while (spacep (p)) p ++; /* If the id starts with 0x accept and ignore it. */ if (p[0] == '0' && p[1] == 'x') p += 2; for (i = 0, j = 0; p[i]; i ++) if (hexdigitp (&p[i])) { p[j] = toupper (p[i]); j ++; } else if (spacep (&p[i])) /* Skip spaces. */ { } else { is_hex_digits = 0; break; } if (is_hex_digits) /* In case we skipped some spaces, add a new NUL terminator. */ { p[j] = 0; /* If we skipped some spaces, make sure that we still have at least 8 characters. */ is_hex_digits = (/* Short keyid. */ strlen (p) == 8 /* Long keyid. */ || strlen (p) == 16 /* Fingerprints are (currently) 32 or 40 characters. */ || strlen (p) >= 32); } } if (is_hex_digits) { int found_one = 0; for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) { int match = 0; if (strlen (p) == 8 || strlen (p) == 16) { u32 kid[2]; char kid_str[17]; keyid_from_pk (node->pkt->pkt.public_key, kid); format_keyid (kid, strlen (p) == 8 ? KF_SHORT : KF_LONG, kid_str, sizeof (kid_str)); if (strcmp (p, kid_str) == 0) match = 1; } else { char fp[2*MAX_FINGERPRINT_LEN + 1]; hexfingerprint (node->pkt->pkt.public_key, fp, sizeof (fp)); if (strcmp (fp, p) == 0) match = 1; } if (match) { if ((node->flag & NODFLG_SELKEY)) node->flag &= ~NODFLG_SELKEY; else node->flag |= NODFLG_SELKEY; found_one = 1; } } if (found_one) return 1; tty_printf (_("No subkey with key ID '%s'.\n"), p); return 0; } if (idx == -1) /* Select all. */ { for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) node->flag |= NODFLG_SELKEY; } else if (idx) /* Toggle selection. */ { for (i = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) if (++i == idx) break; } if (!node) { tty_printf (_("No subkey with index %d\n"), idx); return 0; } for (i = 0, node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) if (++i == idx) { if ((node->flag & NODFLG_SELKEY)) node->flag &= ~NODFLG_SELKEY; else node->flag |= NODFLG_SELKEY; } } } else /* Unselect all. */ { for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) node->flag &= ~NODFLG_SELKEY; } return 1; } static int count_uids_with_flag (KBNODE keyblock, unsigned flag) { KBNODE node; int i = 0; for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && (node->flag & flag)) i++; return i; } static int count_keys_with_flag (KBNODE keyblock, unsigned flag) { KBNODE node; int i = 0; for (node = keyblock; node; node = node->next) if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY || node->pkt->pkttype == PKT_SECRET_SUBKEY) && (node->flag & flag)) i++; return i; } static int count_uids (KBNODE keyblock) { KBNODE node; int i = 0; for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID) i++; return i; } /* * Returns true if there is at least one selected user id */ static int count_selected_uids (KBNODE keyblock) { return count_uids_with_flag (keyblock, NODFLG_SELUID); } static int count_selected_keys (KBNODE keyblock) { return count_keys_with_flag (keyblock, NODFLG_SELKEY); } /* Returns how many real (i.e. not attribute) uids are unmarked. */ static int real_uids_left (KBNODE keyblock) { KBNODE node; int real = 0; for (node = keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & NODFLG_SELUID) && !node->pkt->pkt.user_id->attrib_data) real++; return real; } /* * Ask whether the signature should be revoked. If the user commits this, * flag bit MARK_A is set on the signature and the user ID. */ static void ask_revoke_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node) { int doit = 0; PKT_user_id *uid; PKT_signature *sig = node->pkt->pkt.signature; KBNODE unode = find_prev_kbnode (keyblock, node, PKT_USER_ID); if (!unode) { log_error ("Oops: no user ID for signature\n"); return; } uid = unode->pkt->pkt.user_id; if (opt.with_colons) { if (uid->attrib_data) printf ("uat:::::::::%u %lu", uid->numattribs, uid->attrib_len); else { es_printf ("uid:::::::::"); es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL); } es_printf ("\n"); print_and_check_one_sig_colon (ctrl, keyblock, node, NULL, NULL, NULL, NULL, 1); } else { char *p = utf8_to_native (unode->pkt->pkt.user_id->name, unode->pkt->pkt.user_id->len, 0); tty_printf (_("user ID: \"%s\"\n"), p); xfree (p); tty_printf (_("signed by your key %s on %s%s%s\n"), keystr (sig->keyid), datestr_from_sig (sig), sig->flags.exportable ? "" : _(" (non-exportable)"), ""); } if (sig->flags.expired) { tty_printf (_("This signature expired on %s.\n"), expirestr_from_sig (sig)); /* Use a different question so we can have different help text */ doit = cpr_get_answer_is_yes ("ask_revoke_sig.expired", _("Are you sure you still want to revoke it? (y/N) ")); } else doit = cpr_get_answer_is_yes ("ask_revoke_sig.one", _("Create a revocation certificate for this signature? (y/N) ")); if (doit) { node->flag |= NODFLG_MARK_A; unode->flag |= NODFLG_MARK_A; } } /* * Display all user ids of the current public key together with signatures * done by one of our keys. Then walk over all this sigs and ask the user * whether he wants to revoke this signature. * Return: True when the keyblock has changed. */ static int menu_revsig (ctrl_t ctrl, kbnode_t keyblock) { PKT_signature *sig; PKT_public_key *primary_pk; KBNODE node; int changed = 0; int rc, any, skip = 1, all = !count_selected_uids (keyblock); struct revocation_reason_info *reason = NULL; log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY); /* First check whether we have any signatures at all. */ any = 0; for (node = keyblock; node; node = node->next) { node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A); if (node->pkt->pkttype == PKT_USER_ID) { if (node->flag & NODFLG_SELUID || all) skip = 0; else skip = 1; } else if (!skip && node->pkt->pkttype == PKT_SIGNATURE && ((sig = node->pkt->pkt.signature), have_secret_key_with_kid (ctrl, sig->keyid))) { if ((sig->sig_class & ~3) == 0x10) { any = 1; break; } } } if (!any) { tty_printf (_("Not signed by you.\n")); return 0; } /* FIXME: detect duplicates here */ tty_printf (_("You have signed these user IDs on key %s:\n"), keystr_from_pk (keyblock->pkt->pkt.public_key)); for (node = keyblock; node; node = node->next) { node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A); if (node->pkt->pkttype == PKT_USER_ID) { if (node->flag & NODFLG_SELUID || all) { PKT_user_id *uid = node->pkt->pkt.user_id; /* Hmmm: Should we show only UIDs with a signature? */ tty_printf (" "); tty_print_utf8_string (uid->name, uid->len); tty_printf ("\n"); skip = 0; } else skip = 1; } else if (!skip && node->pkt->pkttype == PKT_SIGNATURE && ((sig = node->pkt->pkt.signature), have_secret_key_with_kid (ctrl, sig->keyid))) { if ((sig->sig_class & ~3) == 0x10) { tty_printf (" "); tty_printf (_("signed by your key %s on %s%s%s\n"), keystr (sig->keyid), datestr_from_sig (sig), sig->flags.exportable ? "" : _(" (non-exportable)"), sig->flags.revocable ? "" : _(" (non-revocable)")); if (sig->flags.revocable) node->flag |= NODFLG_SELSIG; } else if (sig->sig_class == 0x30) { tty_printf (" "); tty_printf (_("revoked by your key %s on %s\n"), keystr (sig->keyid), datestr_from_sig (sig)); } } } tty_printf ("\n"); /* ask */ for (node = keyblock; node; node = node->next) { if (!(node->flag & NODFLG_SELSIG)) continue; ask_revoke_sig (ctrl, keyblock, node); } /* present selected */ any = 0; for (node = keyblock; node; node = node->next) { if (!(node->flag & NODFLG_MARK_A)) continue; if (!any) { any = 1; tty_printf (_("You are about to revoke these signatures:\n")); } if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; tty_printf (" "); tty_print_utf8_string (uid->name, uid->len); tty_printf ("\n"); } else if (node->pkt->pkttype == PKT_SIGNATURE) { sig = node->pkt->pkt.signature; tty_printf (" "); tty_printf (_("signed by your key %s on %s%s%s\n"), keystr (sig->keyid), datestr_from_sig (sig), "", sig->flags.exportable ? "" : _(" (non-exportable)")); } } if (!any) return 0; /* none selected */ if (!cpr_get_answer_is_yes ("ask_revoke_sig.okay", _("Really create the revocation certificates? (y/N) "))) return 0; /* forget it */ reason = ask_revocation_reason (0, 1, 0); if (!reason) { /* user decided to cancel */ return 0; } /* now we can sign the user ids */ reloop: /* (must use this, because we are modifying the list) */ primary_pk = keyblock->pkt->pkt.public_key; for (node = keyblock; node; node = node->next) { KBNODE unode; PACKET *pkt; struct sign_attrib attrib; PKT_public_key *signerkey; if (!(node->flag & NODFLG_MARK_A) || node->pkt->pkttype != PKT_SIGNATURE) continue; unode = find_prev_kbnode (keyblock, node, PKT_USER_ID); log_assert (unode); /* we already checked this */ memset (&attrib, 0, sizeof attrib); attrib.reason = reason; attrib.non_exportable = !node->pkt->pkt.signature->flags.exportable; node->flag &= ~NODFLG_MARK_A; signerkey = xmalloc_secure_clear (sizeof *signerkey); if (get_seckey (ctrl, signerkey, node->pkt->pkt.signature->keyid)) { log_info (_("no secret key\n")); free_public_key (signerkey); continue; } rc = make_keysig_packet (ctrl, &sig, primary_pk, unode->pkt->pkt.user_id, NULL, signerkey, 0x30, 0, 0, sign_mk_attrib, &attrib, NULL); free_public_key (signerkey); if (rc) { write_status_error ("keysig", rc); log_error (_("signing failed: %s\n"), gpg_strerror (rc)); release_revocation_reason_info (reason); return changed; } changed = 1; /* we changed the keyblock */ update_trust = 1; /* Are we revoking our own uid? */ if (primary_pk->keyid[0] == sig->keyid[0] && primary_pk->keyid[1] == sig->keyid[1]) unode->pkt->pkt.user_id->flags.revoked = 1; pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (unode, new_kbnode (pkt), 0); goto reloop; } release_revocation_reason_info (reason); return changed; } /* return 0 if revocation of NODE (which must be a User ID) was successful, non-zero if there was an error. *modified will be set to 1 if a change was made. */ static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node, const struct revocation_reason_info *reason, int *modified) { PKT_public_key *pk = keyblock->pkt->pkt.public_key; gpg_error_t rc; if (node->pkt->pkttype != PKT_USER_ID) { rc = gpg_error (GPG_ERR_NO_USER_ID); write_status_error ("keysig", rc); log_error (_("tried to revoke a non-user ID: %s\n"), gpg_strerror (rc)); return 1; } else { PKT_user_id *uid = node->pkt->pkt.user_id; if (uid->flags.revoked) { char *user = utf8_to_native (uid->name, uid->len, 0); log_info (_("user ID \"%s\" is already revoked\n"), user); xfree (user); } else { PACKET *pkt; PKT_signature *sig; struct sign_attrib attrib; u32 timestamp = make_timestamp (); if (uid->created >= timestamp) { /* Okay, this is a problem. The user ID selfsig was created in the future, so we need to warn the user and set our revocation timestamp one second after that so everything comes out clean. */ log_info (_("WARNING: a user ID signature is dated %d" " seconds in the future\n"), uid->created - timestamp); timestamp = uid->created + 1; } memset (&attrib, 0, sizeof attrib); /* should not need to cast away const here; but revocation_reason_build_cb needs to take a non-const void* in order to meet the function signature for the mksubpkt argument to make_keysig_packet */ attrib.reason = (struct revocation_reason_info *)reason; rc = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x30, timestamp, 0, sign_mk_attrib, &attrib, NULL); if (rc) { write_status_error ("keysig", rc); log_error (_("signing failed: %s\n"), gpg_strerror (rc)); return 1; } else { pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (node, new_kbnode (pkt), 0); #ifndef NO_TRUST_MODELS /* If the trustdb has an entry for this key+uid then the trustdb needs an update. */ if (!update_trust && ((get_validity (ctrl, keyblock, pk, uid, NULL, 0) & TRUST_MASK) >= TRUST_UNDEFINED)) update_trust = 1; #endif /*!NO_TRUST_MODELS*/ node->pkt->pkt.user_id->flags.revoked = 1; if (modified) *modified = 1; } } return 0; } } /* Revoke a user ID (i.e. revoke a user ID selfsig). Return true if keyblock changed. */ static int menu_revuid (ctrl_t ctrl, kbnode_t pub_keyblock) { PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key; KBNODE node; int changed = 0; int rc; struct revocation_reason_info *reason = NULL; size_t valid_uids; /* Note that this is correct as per the RFCs, but nevertheless somewhat meaningless in the real world. 1991 did define the 0x30 sig class, but PGP 2.x did not actually implement it, so it would probably be safe to use v4 revocations everywhere. -ds */ for (node = pub_keyblock; node; node = node->next) if (pk->version > 3 || (node->pkt->pkttype == PKT_USER_ID && node->pkt->pkt.user_id->selfsigversion > 3)) { if ((reason = ask_revocation_reason (0, 1, 4))) break; else goto leave; } /* Too make sure that we do not revoke the last valid UID, we first count how many valid UIDs there are. */ valid_uids = 0; for (node = pub_keyblock; node; node = node->next) valid_uids += node->pkt->pkttype == PKT_USER_ID && ! node->pkt->pkt.user_id->flags.revoked && ! node->pkt->pkt.user_id->flags.expired; reloop: /* (better this way because we are modifying the keyring) */ for (node = pub_keyblock; node; node = node->next) if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_SELUID)) { int modified = 0; /* Make sure that we do not revoke the last valid UID. */ if (valid_uids == 1 && ! node->pkt->pkt.user_id->flags.revoked && ! node->pkt->pkt.user_id->flags.expired) { log_error (_("Cannot revoke the last valid user ID.\n")); goto leave; } rc = core_revuid (ctrl, pub_keyblock, node, reason, &modified); if (rc) goto leave; if (modified) { node->flag &= ~NODFLG_SELUID; changed = 1; goto reloop; } } if (changed) commit_kbnode (&pub_keyblock); leave: release_revocation_reason_info (reason); return changed; } /* * Revoke the whole key. */ static int menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock) { PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key; int rc, changed = 0; struct revocation_reason_info *reason; PACKET *pkt; PKT_signature *sig; if (pk->flags.revoked) { tty_printf (_("Key %s is already revoked.\n"), keystr_from_pk (pk)); return 0; } reason = ask_revocation_reason (1, 0, 0); /* user decided to cancel */ if (!reason) return 0; rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk, 0x20, 0, 0, revocation_reason_build_cb, reason, NULL); if (rc) { write_status_error ("keysig", rc); log_error (_("signing failed: %s\n"), gpg_strerror (rc)); goto scram; } changed = 1; /* we changed the keyblock */ pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (pub_keyblock, new_kbnode (pkt), 0); commit_kbnode (&pub_keyblock); update_trust = 1; scram: release_revocation_reason_info (reason); return changed; } static int menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock) { PKT_public_key *mainpk; KBNODE node; int changed = 0; int rc; struct revocation_reason_info *reason = NULL; reason = ask_revocation_reason (1, 0, 0); if (!reason) return 0; /* User decided to cancel. */ reloop: /* (better this way because we are modifying the keyring) */ mainpk = pub_keyblock->pkt->pkt.public_key; for (node = pub_keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY && (node->flag & NODFLG_SELKEY)) { PACKET *pkt; PKT_signature *sig; PKT_public_key *subpk = node->pkt->pkt.public_key; struct sign_attrib attrib; if (subpk->flags.revoked) { tty_printf (_("Subkey %s is already revoked.\n"), keystr_from_pk (subpk)); continue; } memset (&attrib, 0, sizeof attrib); attrib.reason = reason; node->flag &= ~NODFLG_SELKEY; rc = make_keysig_packet (ctrl, &sig, mainpk, NULL, subpk, mainpk, 0x28, 0, 0, sign_mk_attrib, &attrib, NULL); if (rc) { write_status_error ("keysig", rc); log_error (_("signing failed: %s\n"), gpg_strerror (rc)); release_revocation_reason_info (reason); return changed; } changed = 1; /* we changed the keyblock */ pkt = xmalloc_clear (sizeof *pkt); pkt->pkttype = PKT_SIGNATURE; pkt->pkt.signature = sig; insert_kbnode (node, new_kbnode (pkt), 0); goto reloop; } } commit_kbnode (&pub_keyblock); /* No need to set update_trust here since signing keys no longer are used to certify other keys, so there is no change in trust when revoking/removing them */ release_revocation_reason_info (reason); return changed; } /* Note that update_ownertrust is going to mark the trustdb dirty when enabling or disabling a key. This is arguably sub-optimal as disabled keys are still counted in the web of trust, but perhaps not worth adding extra complexity to change. -ds */ #ifndef NO_TRUST_MODELS static int enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable) { PKT_public_key *pk = find_kbnode (keyblock, PKT_PUBLIC_KEY)->pkt->pkt.public_key; unsigned int trust, newtrust; trust = newtrust = get_ownertrust (ctrl, pk); newtrust &= ~TRUST_FLAG_DISABLED; if (disable) newtrust |= TRUST_FLAG_DISABLED; if (trust == newtrust) return 0; /* already in that state */ update_ownertrust (ctrl, pk, newtrust); return 0; } #endif /*!NO_TRUST_MODELS*/ static void menu_showphoto (ctrl_t ctrl, kbnode_t keyblock) { KBNODE node; int select_all = !count_selected_uids (keyblock); int count = 0; PKT_public_key *pk = NULL; /* Look for the public key first. We have to be really, really, explicit as to which photo this is, and what key it is a UID on since people may want to sign it. */ for (node = keyblock; node; node = node->next) { if (node->pkt->pkttype == PKT_PUBLIC_KEY) pk = node->pkt->pkt.public_key; else if (node->pkt->pkttype == PKT_USER_ID) { PKT_user_id *uid = node->pkt->pkt.user_id; count++; if ((select_all || (node->flag & NODFLG_SELUID)) && uid->attribs != NULL) { int i; for (i = 0; i < uid->numattribs; i++) { byte type; u32 size; if (uid->attribs[i].type == ATTRIB_IMAGE && parse_image_header (&uid->attribs[i], &type, &size)) { tty_printf (_("Displaying %s photo ID of size %ld for " "key %s (uid %d)\n"), image_type_to_string (type, 1), (ulong) size, keystr_from_pk (pk), count); show_photos (ctrl, &uid->attribs[i], 1, pk, uid); } } } } } } diff --git a/scd/app-p15.c b/scd/app-p15.c index 5310af84d..0e92edf56 100644 --- a/scd/app-p15.c +++ b/scd/app-p15.c @@ -1,6312 +1,6312 @@ /* app-p15.c - The pkcs#15 card application. * Copyright (C) 2005 Free Software Foundation, Inc. * Copyright (C) 2020, 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 */ /* Information pertaining to the BELPIC developer card samples: Unblock PUK: "222222111111" Reset PIN: "333333111111") e.g. the APDUs 00:20:00:02:08:2C:33:33:33:11:11:11:FF and 00:24:01:01:08:24:12:34:FF:FF:FF:FF:FF should change the PIN into 1234. */ #include #include #include #include #include #include #include "scdaemon.h" #include "iso7816.h" #include "../common/i18n.h" #include "../common/tlv.h" #include "../common/host2net.h" #include "../common/openpgpdefs.h" #include "apdu.h" /* fixme: we should move the card detection to a separate file */ static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3"; static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8"; static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9"; static const char oid_kp_ms_documentSigning[] = "1.3.6.1.4.1.311.10.3.12"; static const char oid_kp_ms_old_documentSigning[] = "1.3.6.1.4.1.311.3.10.3.12"; static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4"; static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1"; static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2"; static const char oid_kp_ms_smartcardLogon[] = "1.3.6.1.4.1.311.20.2.2"; static const char oid_kp_anyExtendedKeyUsage[] = "2.5.29.37.0"; static const char oid_kp_gpgUsageCert[] = "1.3.6.1.4.1.11591.2.6.1"; static const char oid_kp_gpgUsageSign[] = "1.3.6.1.4.1.11591.2.6.2"; static const char oid_kp_gpgUsageEncr[] = "1.3.6.1.4.1.11591.2.6.3"; static const char oid_kp_gpgUsageAuth[] = "1.3.6.1.4.1.11591.2.6.4"; /* Types of cards we know and which needs special treatment. */ typedef enum { CARD_TYPE_UNKNOWN, CARD_TYPE_TCOS, CARD_TYPE_MICARDO, CARD_TYPE_CARDOS_50, CARD_TYPE_CARDOS_53, CARD_TYPE_AET, /* A.E.T. Europe JCOP card. */ CARD_TYPE_BELPIC /* Belgian eID card specs. */ } card_type_t; /* The OS of card as specified by card_type_t is not always * sufficient. Thus we also distinguish the actual product build upon * the given OS. */ typedef enum { CARD_PRODUCT_UNKNOWN, CARD_PRODUCT_RSCS, /* Rohde&Schwarz Cybersecurity */ CARD_PRODUCT_DTRUST, /* D-Trust GmbH (bundesdruckerei.de) */ CARD_PRODUCT_GENUA /* GeNUA mbH */ } card_product_t; /* A list card types with ATRs noticed with these cards. */ #define X(a) ((unsigned char const *)(a)) static struct { size_t atrlen; unsigned char const *atr; card_type_t type; } card_atr_list[] = { { 19, X("\x3B\xBA\x13\x00\x81\x31\x86\x5D\x00\x64\x05\x0A\x02\x01\x31\x80" "\x90\x00\x8B"), CARD_TYPE_TCOS }, /* SLE44 */ { 19, X("\x3B\xBA\x14\x00\x81\x31\x86\x5D\x00\x64\x05\x14\x02\x02\x31\x80" "\x90\x00\x91"), CARD_TYPE_TCOS }, /* SLE66S */ { 19, X("\x3B\xBA\x96\x00\x81\x31\x86\x5D\x00\x64\x05\x60\x02\x03\x31\x80" "\x90\x00\x66"), CARD_TYPE_TCOS }, /* SLE66P */ { 27, X("\x3B\xFF\x94\x00\xFF\x80\xB1\xFE\x45\x1F\x03\x00\x68\xD2\x76\x00" "\x00\x28\xFF\x05\x1E\x31\x80\x00\x90\x00\x23"), CARD_TYPE_MICARDO }, /* German BMI card */ { 19, X("\x3B\x6F\x00\xFF\x00\x68\xD2\x76\x00\x00\x28\xFF\x05\x1E\x31\x80" "\x00\x90\x00"), CARD_TYPE_MICARDO }, /* German BMI card (ATR due to reader problem) */ { 26, X("\x3B\xFE\x94\x00\xFF\x80\xB1\xFA\x45\x1F\x03\x45\x73\x74\x45\x49" "\x44\x20\x76\x65\x72\x20\x31\x2E\x30\x43"), CARD_TYPE_MICARDO }, /* EstEID (Estonian Big Brother card) */ { 11, X("\x3b\xd2\x18\x00\x81\x31\xfe\x58\xc9\x01\x14"), CARD_TYPE_CARDOS_50 }, /* CardOS 5.0 */ { 11, X("\x3b\xd2\x18\x00\x81\x31\xfe\x58\xc9\x03\x16"), CARD_TYPE_CARDOS_53 }, /* CardOS 5.3 */ { 24, X("\x3b\xfe\x18\x00\x00\x80\x31\xfe\x45\x53\x43\x45" "\x36\x30\x2d\x43\x44\x30\x38\x31\x2d\x6e\x46\xa9"), CARD_TYPE_AET }, { 0 } }; #undef X /* Macro to test for CardOS 5.0 and 5.3. */ #define IS_CARDOS_5(a) ((a)->app_local->card_type == CARD_TYPE_CARDOS_50 \ || (a)->app_local->card_type == CARD_TYPE_CARDOS_53) /* The default PKCS-15 home DF */ #define DEFAULT_HOME_DF 0x5015 /* The AID of PKCS15. */ static char const pkcs15_aid[] = { 0xA0, 0, 0, 0, 0x63, 0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 }; /* The Belgian eID variant - they didn't understood why a shared AID is useful for a standard. Oh well. */ static char const pkcs15be_aid[] = { 0xA0, 0, 0, 0x01, 0x77, 0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 }; /* The PIN types as defined in pkcs#15 v1.1 */ typedef enum { PIN_TYPE_BCD = 0, PIN_TYPE_ASCII_NUMERIC = 1, PIN_TYPE_UTF8 = 2, PIN_TYPE_HALF_NIBBLE_BCD = 3, PIN_TYPE_ISO9564_1 = 4 } pin_type_t; /* The AuthenticationTypes as defined in pkcs#15 v1.1 (6.8.1) */ typedef enum { AUTH_TYPE_PIN = -1, AUTH_TYPE_BIOMETRIC = 0, AUTH_TYPE_AUTHKEY = 1, AUTH_TYPE_EXTERNAL = 2, } auth_type_t; /* A bit array with for the key usage flags from the commonKeyAttributes. */ struct keyusage_flags_s { unsigned int encrypt: 1; unsigned int decrypt: 1; unsigned int sign: 1; unsigned int sign_recover: 1; unsigned int wrap: 1; unsigned int unwrap: 1; unsigned int verify: 1; unsigned int verify_recover: 1; unsigned int derive: 1; unsigned int non_repudiation: 1; }; typedef struct keyusage_flags_s keyusage_flags_t; /* A bit array with for the key access flags from the commonKeyAttributes. */ struct keyaccess_flags_s { unsigned int any:1; /* Any access flag set. */ unsigned int sensitive:1; unsigned int extractable:1; unsigned int always_sensitive:1; unsigned int never_extractable:1; unsigned int local:1; }; typedef struct keyaccess_flags_s keyaccess_flags_t; /* A bit array with for the gpg usage flags. */ struct gpgusage_flags_s { unsigned int any:1; /* Any of the next flags are set. */ unsigned int cert:1; /* 1.3.6.1.4.1.11591.2.6.1 */ unsigned int sign:1; /* 1.3.6.1.4.1.11591.2.6.2 */ unsigned int encr:1; /* 1.3.6.1.4.1.11591.2.6.3 */ unsigned int auth:1; /* 1.3.6.1.4.1.11591.2.6.4 */ }; typedef struct gpgusage_flags_s gpgusage_flags_t; /* This is an object to store information about a Certificate Directory File (CDF) in a format suitable for further processing by us. To keep memory management, simple we use a linked list of items; i.e. one such object represents one certificate and the list the entire CDF. */ struct cdf_object_s { /* Link to next item when used in a linked list. */ struct cdf_object_s *next; /* Flags to indicate whether fields are valid. */ unsigned int have_off:1; /* Length and allocated buffer with the Id of this object. * This field is used for X.509 in PKCS#11 to make it easier to * match a private key with a certificate. */ size_t objidlen; unsigned char *objid; /* Length and allocated buffer with the authId of this object or NULL if no authID is known. */ size_t authidlen; unsigned char *authid; /* NULL or the malloced label of this object. */ char *label; /* To avoid reading and parsing a certificate more than once, we * cache the ksba object. */ ksba_cert_t cert; /* The offset and length of the object. They are only valid if HAVE_OFF is true and set to 0 if HAVE_OFF is false. */ unsigned long off, len; /* The length of the path as given in the CDF and the path itself. path[0] is the top DF (usually 0x3f00). The path will never be empty. */ size_t pathlen; unsigned short path[1]; }; typedef struct cdf_object_s *cdf_object_t; /* This is an object to store information about a Private Key Directory File (PrKDF) in a format suitable for further processing by us. To keep memory management, simple we use a linked list of items; i.e. one such object represents one certificate and the list the entire PrKDF. */ struct prkdf_object_s { /* Link to next item when used in a linked list. */ struct prkdf_object_s *next; /* Flags to indicate whether fields are valid. */ unsigned int keygrip_valid:1; unsigned int key_reference_valid:1; unsigned int have_off:1; unsigned int have_keytime:1; /* Flag indicating that the corresponding PIN has already been * verified. Note that for cards which are able to return the * verification stus, this flag is not used. */ unsigned int pin_verified:1; /* PKCS#15 info whether this is an EC key. Default is RSA. Note * that there is also a KEYALGO field which is derived from the * publick key via Libgcrypt. */ unsigned int is_ecc:1; /* The key's usage flags. */ keyusage_flags_t usageflags; /* The key's access flags. */ keyaccess_flags_t accessflags; /* Extended key usage flags. Only used if .valid is set. This * information is computed from an associated certificate15. */ struct { unsigned int valid:1; unsigned int sign:1; unsigned int encr:1; unsigned int auth:1; } extusage; /* OpenPGP key features for this key. This is taken from special * extended key usage flags different from those tracked in EXTUSAGE * above. There is also no valid flag as in EXTUSAGE. */ gpgusage_flags_t gpgusage; /* The keygrip of the key. This is used as a cache. */ char keygrip[2*KEYGRIP_LEN+1]; /* A malloced algorithm string or NULL if not known. */ char *keyalgostr; /* The Gcrypt algo identifier for the key. It is valid if the * keygrip is also valid. See also is_ecc above. */ int keyalgo; /* The length of the key in bits (e.g. for RSA the length of the * modulus). It is valid if the keygrip is also valid. */ unsigned int keynbits; /* The creation time of the key or 0 if not known. */ u32 keytime; /* Malloced CN from the Subject-DN of the corresponding certificate * or NULL if not known. */ char *common_name; /* Malloced SerialNumber from the Subject-DN of the corresponding * certificate or NULL if not known. */ char *serial_number; /* KDF/KEK parameter for OpenPGP's ECDH. First byte is zero if not * available. .*/ unsigned char ecdh_kdf[4]; /* Length and allocated buffer with the Id of this object. */ size_t objidlen; unsigned char *objid; /* Length and allocated buffer with the authId of this object or NULL if no authID is known. */ size_t authidlen; unsigned char *authid; /* NULL or the malloced label of this object. */ char *label; /* The keyReference and a flag telling whether it is valid. */ unsigned long key_reference; /* The offset and length of the object. They are only valid if * HAVE_OFF is true otherwise they are set to 0. */ unsigned long off, len; /* The length of the path as given in the PrKDF and the path itself. path[0] is the top DF (usually 0x3f00). */ size_t pathlen; unsigned short path[1]; }; typedef struct prkdf_object_s *prkdf_object_t; typedef struct prkdf_object_s *pukdf_object_t; /* This is an object to store information about a Authentication Object Directory File (AODF) in a format suitable for further processing by us. To keep memory management, simple we use a linked list of items; i.e. one such object represents one authentication object and the list the entire AOKDF. */ struct aodf_object_s { /* Link to next item when used in a linked list. */ struct aodf_object_s *next; /* Flags to indicate whether fields are valid. */ unsigned int have_off:1; /* Length and allocated buffer with the Id of this object. */ size_t objidlen; unsigned char *objid; /* Length and allocated buffer with the authId of this object or NULL if no authID is known. */ size_t authidlen; unsigned char *authid; /* NULL or the malloced label of this object. */ char *label; /* The file ID of this AODF. */ unsigned short fid; /* The type of this authentication object. */ auth_type_t auth_type; /* Info used for AUTH_TYPE_PIN: */ /* The PIN Flags. */ struct { unsigned int case_sensitive: 1; unsigned int local: 1; unsigned int change_disabled: 1; unsigned int unblock_disabled: 1; unsigned int initialized: 1; unsigned int needs_padding: 1; unsigned int unblocking_pin: 1; unsigned int so_pin: 1; unsigned int disable_allowed: 1; unsigned int integrity_protected: 1; unsigned int confidentiality_protected: 1; unsigned int exchange_ref_data: 1; } pinflags; /* The PIN Type. */ pin_type_t pintype; /* The minimum length of a PIN. */ unsigned long min_length; /* The stored length of a PIN. */ unsigned long stored_length; /* The maximum length of a PIN and a flag telling whether it is valid. */ unsigned long max_length; int max_length_valid; /* The pinReference and a flag telling whether it is valid. */ unsigned long pin_reference; int pin_reference_valid; /* The padChar and a flag telling whether it is valid. */ char pad_char; int pad_char_valid; /* The offset and length of the object. They are only valid if HAVE_OFF is true and set to 0 if HAVE_OFF is false. */ unsigned long off, len; /* The length of the path as given in the Aodf and the path itself. path[0] is the top DF (usually 0x3f00). PATH is optional and thus may be NULL. Malloced.*/ size_t pathlen; unsigned short *path; /* Info used for AUTH_TYPE_AUTHKEY: */ }; typedef struct aodf_object_s *aodf_object_t; /* Context local to this application. */ struct app_local_s { /* The home DF. Note, that we don't yet support a multilevel hierarchy. Thus we assume this is directly below the MF. */ unsigned short home_df; /* The type of the card's OS. */ card_type_t card_type; /* The vendor's product. */ card_product_t card_product; /* Flag indicating that extended_mode is not supported. */ unsigned int no_extended_mode : 1; /* Flag indicating whether we may use direct path selection. */ unsigned int direct_path_selection : 1; /* Flag indicating whether the card has any key with a gpgusage set. */ unsigned int any_gpgusage : 1; /* Structure with the EFIDs of the objects described in the ODF file. */ struct { unsigned short private_keys; unsigned short public_keys; unsigned short trusted_public_keys; unsigned short secret_keys; unsigned short certificates; unsigned short trusted_certificates; unsigned short useful_certificates; unsigned short data_objects; unsigned short auth_objects; } odf; /* The PKCS#15 serialnumber from EF(TokenInfo) or NULL. Malloced. */ unsigned char *serialno; size_t serialnolen; /* The manufacturerID from the TokenInfo EF. Malloced or NULL. */ char *manufacturer_id; /* The label from the TokenInfo EF. Malloced or NULL. */ char *token_label; /* The tokenflags from the TokenInfo EF. Malloced or NULL. */ unsigned char *tokenflags; unsigned int tokenflagslen; /* Information on all certificates. */ cdf_object_t certificate_info; /* Information on all trusted certificates. */ cdf_object_t trusted_certificate_info; /* Information on all useful certificates. */ cdf_object_t useful_certificate_info; /* Information on all public keys. */ prkdf_object_t public_key_info; /* Information on all private keys. */ pukdf_object_t private_key_info; /* Information on all authentication objects. */ aodf_object_t auth_object_info; }; /*** Local prototypes. ***/ static gpg_error_t select_ef_by_path (app_t app, const unsigned short *path, size_t pathlen); static gpg_error_t keygrip_from_prkdf (app_t app, prkdf_object_t prkdf); static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf, unsigned char **r_cert, size_t *r_certlen); static char *get_dispserialno (app_t app, prkdf_object_t prkdf); static gpg_error_t do_getattr (app_t app, ctrl_t ctrl, const char *name); static const char * cardtype2str (card_type_t cardtype) { switch (cardtype) { case CARD_TYPE_UNKNOWN: return ""; case CARD_TYPE_TCOS: return "TCOS"; case CARD_TYPE_MICARDO: return "Micardo"; case CARD_TYPE_CARDOS_50: return "CardOS 5.0"; case CARD_TYPE_CARDOS_53: return "CardOS 5.3"; case CARD_TYPE_BELPIC: return "Belgian eID"; case CARD_TYPE_AET: return "AET"; } return ""; } static const char * cardproduct2str (card_product_t cardproduct) { switch (cardproduct) { case CARD_PRODUCT_UNKNOWN: return ""; case CARD_PRODUCT_RSCS: return "R&S"; case CARD_PRODUCT_DTRUST: return "D-Trust"; case CARD_PRODUCT_GENUA: return "GeNUA"; } return ""; } /* Release the CDF object A */ static void release_cdflist (cdf_object_t a) { while (a) { cdf_object_t tmp = a->next; ksba_free (a->cert); xfree (a->objid); xfree (a->authid); xfree (a->label); xfree (a); a = tmp; } } /* Release the PrKDF object A. */ static void release_prkdflist (prkdf_object_t a) { while (a) { prkdf_object_t tmp = a->next; xfree (a->keyalgostr); xfree (a->common_name); xfree (a->serial_number); xfree (a->objid); xfree (a->authid); xfree (a->label); xfree (a); a = tmp; } } static void release_pukdflist (pukdf_object_t a) { release_prkdflist (a); } /* Release just one aodf object. */ void release_aodf_object (aodf_object_t a) { if (a) { xfree (a->objid); xfree (a->authid); xfree (a->label); xfree (a->path); xfree (a); } } /* Release the AODF list A. */ static void release_aodflist (aodf_object_t a) { while (a) { aodf_object_t tmp = a->next; release_aodf_object (a); a = tmp; } } static void release_lists (app_t app) { release_cdflist (app->app_local->certificate_info); app->app_local->certificate_info = NULL; release_cdflist (app->app_local->trusted_certificate_info); app->app_local->trusted_certificate_info = NULL; release_cdflist (app->app_local->useful_certificate_info); app->app_local->useful_certificate_info = NULL; release_pukdflist (app->app_local->public_key_info); app->app_local->public_key_info = NULL; release_prkdflist (app->app_local->private_key_info); app->app_local->private_key_info = NULL; release_aodflist (app->app_local->auth_object_info); app->app_local->auth_object_info = NULL; } static void release_tokeninfo (app_t app) { xfree (app->app_local->manufacturer_id); app->app_local->manufacturer_id = NULL; xfree (app->app_local->token_label); app->app_local->token_label = NULL; xfree (app->app_local->tokenflags); app->app_local->tokenflags = NULL; xfree (app->app_local->serialno); app->app_local->serialno = NULL; } /* Release all local resources. */ static void do_deinit (app_t app) { if (app && app->app_local) { release_lists (app); release_tokeninfo (app); xfree (app->app_local); app->app_local = NULL; } } /* Do a select and a read for the file with EFID. EFID_DESC is a desctription of the EF to be used with error messages. On success BUFFER and BUFLEN contain the entire content of the EF. The caller must free BUFFER only on success. If EFID is 0 no seelct is done. */ static gpg_error_t select_and_read_binary (app_t app, unsigned short efid, const char *efid_desc, unsigned char **buffer, size_t *buflen) { gpg_error_t err; int sw; if (efid) { err = select_ef_by_path (app, &efid, 1); if (err) { log_error ("p15: error selecting %s (0x%04X): %s\n", efid_desc, efid, gpg_strerror (err)); return err; } } err = iso7816_read_binary_ext (app_get_slot (app), 0, 0, 0, buffer, buflen, &sw); if (err) log_error ("p15: error reading %s (0x%04X): %s (sw=%04X)\n", efid_desc, efid, gpg_strerror (err), sw); return err; } /* If EFID is not 0 do a select and then read the record RECNO. * EFID_DESC is a description of the EF to be used with error * messages. On success BUFFER and BUFLEN contain the entire content * of the EF. The caller must free BUFFER only on success. */ static gpg_error_t select_and_read_record (app_t app, unsigned short efid, int recno, const char *efid_desc, unsigned char **buffer, size_t *buflen, int *r_sw) { gpg_error_t err; - int sw; + int sw = 0; if (r_sw) *r_sw = 0x9000; if (efid) { err = select_ef_by_path (app, &efid, 1); if (err) { log_error ("p15: error selecting %s (0x%04X): %s\n", efid_desc, efid, gpg_strerror (err)); if (r_sw) *r_sw = sw; return err; } } err = iso7816_read_record_ext (app_get_slot (app), recno, 1, 0, buffer, buflen, &sw); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) ; else if (err && sw == SW_FILE_STRUCT) ; else log_error ("p15: error reading %s (0x%04X) record %d: %s (sw=%04X)\n", efid_desc, efid, recno, gpg_strerror (err), sw); if (r_sw) *r_sw = sw; return err; } /* On CardOS with a Linear TLV file structure the records starts * with some tag (often the record number) followed by the length * byte for this record. Detect and remove this prefix. */ if (*buflen > 2 && (*buffer)[0] != 0x30 && (*buffer)[1] == *buflen - 2) { memmove (*buffer, *buffer + 2, *buflen - 2); *buflen = *buflen - 2; } return 0; } /* This function calls select file to read a file using a complete path which may or may not start at the master file (MF). */ static gpg_error_t select_ef_by_path (app_t app, const unsigned short *path, size_t pathlen) { gpg_error_t err; int i, j; if (!pathlen) return gpg_error (GPG_ERR_INV_VALUE); /* log_debug ("%s: path=", __func__); */ /* for (j=0; j < pathlen; j++) */ /* log_printf ("%s%04hX", j? "/":"", path[j]); */ /* log_printf ("%s\n",app->app_local->direct_path_selection?" (direct)":"");*/ if (app->app_local->direct_path_selection) { if (pathlen && *path == 0x3f00 ) { if (pathlen == 1) err = iso7816_select_mf (app_get_slot (app)); else err = iso7816_select_path (app_get_slot (app), path+1, pathlen-1, 0); } else err = iso7816_select_path (app_get_slot (app), path, pathlen, app->app_local->home_df); if (err) { log_error ("p15: error selecting path "); goto err_print_path; } } else if (pathlen > 1 && path[0] == 0x3fff) { err = iso7816_select_file (app_get_slot (app), 0x3f00, 0); if (err) { log_error ("p15: error selecting part %d from path ", 0); goto err_print_path; } path++; pathlen--; for (i=0; i < pathlen; i++) { err = iso7816_select_file (app_get_slot (app), path[i], (i+1 == pathlen)? 2 : 1); if (err) { log_error ("p15: error selecting part %d from path ", i); goto err_print_path; } } } else { if (pathlen && *path != 0x3f00 ) log_error ("p15: warning: relative path select not yet implemented\n"); /* FIXME: Use home_df. */ for (i=0; i < pathlen; i++) { err = iso7816_select_file (app_get_slot (app), path[i], !(i+1 == pathlen)); if (err) { log_error ("p15: error selecting part %d from path ", i); goto err_print_path; } } } return 0; err_print_path: if (pathlen && *path != 0x3f00 ) log_printf ("3F00/"); else log_printf ("%04hX/", app->app_local->home_df); for (j=0; j < pathlen; j++) log_printf ("%s%04hX", j? "/":"", path[j]); log_printf (": %s\n", gpg_strerror (err)); return err; } /* Parse a cert Id string (or a key Id string) and return the binary object Id string in a newly allocated buffer stored at R_OBJID and R_OBJIDLEN. On Error NULL will be stored there and an error code returned. On success caller needs to free the buffer at R_OBJID. */ static gpg_error_t parse_certid (app_t app, const char *certid, unsigned char **r_objid, size_t *r_objidlen) { char tmpbuf[10]; const char *s; size_t objidlen; unsigned char *objid; int i; *r_objid = NULL; *r_objidlen = 0; if (certid[0] != 'P' && strlen (certid) == 40) /* This is a keygrip. */ { prkdf_object_t prkdf; for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) if (!keygrip_from_prkdf (app, prkdf) && !strcmp (certid, prkdf->keygrip)) break; if (!prkdf || !prkdf->objidlen || !prkdf->objid) return gpg_error (GPG_ERR_NOT_FOUND); objidlen = prkdf->objidlen; objid = xtrymalloc (objidlen); if (!objid) return gpg_error_from_syserror (); memcpy (objid, prkdf->objid, prkdf->objidlen); } else /* This is a usual keyref. */ { if (app->app_local->home_df != DEFAULT_HOME_DF) snprintf (tmpbuf, sizeof tmpbuf, "P15-%04X.", (unsigned int)(app->app_local->home_df & 0xffff)); else strcpy (tmpbuf, "P15."); if (strncmp (certid, tmpbuf, strlen (tmpbuf)) ) { if (!strncmp (certid, "P15.", 4) || (!strncmp (certid, "P15-", 4) && hexdigitp (certid+4) && hexdigitp (certid+5) && hexdigitp (certid+6) && hexdigitp (certid+7) && certid[8] == '.')) return gpg_error (GPG_ERR_NOT_FOUND); return gpg_error (GPG_ERR_INV_ID); } certid += strlen (tmpbuf); for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++) ; if (*s || !objidlen || (objidlen%2)) return gpg_error (GPG_ERR_INV_ID); objidlen /= 2; objid = xtrymalloc (objidlen); if (!objid) return gpg_error_from_syserror (); for (s=certid, i=0; i < objidlen; i++, s+=2) objid[i] = xtoi_2 (s); } *r_objid = objid; *r_objidlen = objidlen; return 0; } /* Find a certificate object by its object ID and store a pointer to * it at R_CDF. */ static gpg_error_t cdf_object_from_objid (app_t app, size_t objidlen, const unsigned char *objid, cdf_object_t *r_cdf) { cdf_object_t cdf; for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) break; if (!cdf) for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) break; if (!cdf) for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) break; if (!cdf) return gpg_error (GPG_ERR_NOT_FOUND); *r_cdf = cdf; return 0; } /* Find a certificate object by its label and store a pointer to it at * R_CDF. */ static gpg_error_t cdf_object_from_label (app_t app, const char *label, cdf_object_t *r_cdf) { cdf_object_t cdf; if (!label) return gpg_error (GPG_ERR_NOT_FOUND); for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) if (cdf->label && !strcmp (cdf->label, label)) break; if (!cdf) for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next) if (cdf->label && !strcmp (cdf->label, label)) break; if (!cdf) for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next) if (cdf->label && !strcmp (cdf->label, label)) break; if (!cdf) return gpg_error (GPG_ERR_NOT_FOUND); *r_cdf = cdf; return 0; } /* Find a certificate object by the certificate ID CERTID and store a * pointer to it at R_CDF. */ static gpg_error_t cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf) { gpg_error_t err; size_t objidlen; unsigned char *objid; cdf_object_t cdf; prkdf_object_t prkdf; err = parse_certid (app, certid, &objid, &objidlen); if (err) return err; err = cdf_object_from_objid (app, objidlen, objid, &cdf); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* Try again by finding the certid in the prkdf and matching by * label. */ for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen)) break; if (prkdf) err = cdf_object_from_label (app, prkdf->label, &cdf); } xfree (objid); if (err) return err; *r_cdf = cdf; return 0; } /* Find a private key object by the key Id string KEYIDSTR and store a pointer to it at R_PRKDF. */ static gpg_error_t prkdf_object_from_keyidstr (app_t app, const char *keyidstr, prkdf_object_t *r_prkdf) { gpg_error_t err; size_t objidlen; unsigned char *objid; prkdf_object_t prkdf; err = parse_certid (app, keyidstr, &objid, &objidlen); if (err) return err; for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen)) break; xfree (objid); if (!prkdf) return gpg_error (GPG_ERR_NOT_FOUND); *r_prkdf = prkdf; return 0; } /* Read and parse the Object Directory File and store away the pointers. ODF_FID shall contain the FID of the ODF. Example of such a file: A0 06 30 04 04 02 60 34 = Private Keys A4 06 30 04 04 02 60 35 = Certificates A5 06 30 04 04 02 60 36 = Trusted Certificates A7 06 30 04 04 02 60 37 = Data Objects A8 06 30 04 04 02 60 38 = Auth Objects These are all PathOrObjects using the path CHOICE element. The paths are octet strings of length 2. Using this Path CHOICE element is recommended, so we only implement that for now. */ static gpg_error_t read_ef_odf (app_t app, unsigned short odf_fid) { gpg_error_t err; unsigned char *buffer, *p; size_t buflen, n; unsigned short value; size_t offset; unsigned short home_df = 0; app->app_local->odf.private_keys = 0; app->app_local->odf.public_keys = 0; app->app_local->odf.trusted_public_keys = 0; app->app_local->odf.secret_keys = 0; app->app_local->odf.certificates = 0; app->app_local->odf.trusted_certificates = 0; app->app_local->odf.useful_certificates = 0; app->app_local->odf.data_objects = 0; app->app_local->odf.auth_objects = 0; err = select_and_read_binary (app, odf_fid, "ODF", &buffer, &buflen); if (err) return err; if (buflen < 8) { log_error ("p15: error: ODF too short\n"); xfree (buffer); return gpg_error (GPG_ERR_INV_OBJ); } home_df = app->app_local->home_df; p = buffer; while (buflen && *p && *p != 0xff) { if ( buflen >= 8 && (p[0] & 0xf0) == 0xA0 && !memcmp (p+1, "\x06\x30\x04\x04\x02", 5) ) { offset = 6; } else if ( buflen >= 12 && (p[0] & 0xf0) == 0xA0 && !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00", 7) && (!home_df || home_df == ((p[8]<<8)|p[9])) ) { /* FIXME: Is this hack still required? */ /* If we do not know the home DF, we take it from the first * ODF object. Here are sample values: * a0 0a 30 08 0406 3f00 5015 4401 * a1 0a 30 08 0406 3f00 5015 4411 * a4 0a 30 08 0406 3f00 5015 4441 * a5 0a 30 08 0406 3f00 5015 4451 * a8 0a 30 08 0406 3f00 5015 4481 * 00000000 */ if (!home_df) { home_df = ((p[8]<<8)|p[9]); app->app_local->home_df = home_df; log_info ("p15: application directory detected as 0x%04hX\n", home_df); /* We assume that direct path selection is possible. */ app->app_local->direct_path_selection = 1; } /* We only allow a full path if all files are at the same level and below the home directory. To extend this we would need to make use of new data type capable of keeping a full path. */ offset = 10; } else { log_printhex (p, buflen, "p15: ODF format not supported:"); xfree (buffer); return gpg_error (GPG_ERR_INV_OBJ); } switch ((p[0] & 0x0f)) { case 0: value = app->app_local->odf.private_keys; break; case 1: value = app->app_local->odf.public_keys; break; case 2: value = app->app_local->odf.trusted_public_keys; break; case 3: value = app->app_local->odf.secret_keys; break; case 4: value = app->app_local->odf.certificates; break; case 5: value = app->app_local->odf.trusted_certificates; break; case 6: value = app->app_local->odf.useful_certificates; break; case 7: value = app->app_local->odf.data_objects; break; case 8: value = app->app_local->odf.auth_objects; break; default: value = 0; break; } if (value) { log_error ("p15: duplicate object type %d in ODF ignored\n", (p[0]&0x0f)); continue; } value = ((p[offset] << 8) | p[offset+1]); switch ((p[0] & 0x0f)) { case 0: app->app_local->odf.private_keys = value; break; case 1: app->app_local->odf.public_keys = value; break; case 2: app->app_local->odf.trusted_public_keys = value; break; case 3: app->app_local->odf.secret_keys = value; break; case 4: app->app_local->odf.certificates = value; break; case 5: app->app_local->odf.trusted_certificates = value; break; case 6: app->app_local->odf.useful_certificates = value; break; case 7: app->app_local->odf.data_objects = value; break; case 8: app->app_local->odf.auth_objects = value; break; default: log_error ("p15: unknown object type %d in ODF ignored\n", (p[0]&0x0f)); } offset += 2; if (buflen < offset) break; p += offset; buflen -= offset; } if (buflen) { /* Print a warning if non-null garbage is left over. */ for (n=0; n < buflen && !p[n]; n++) ; if (n < buflen) { log_info ("p15: warning: garbage detected at end of ODF: "); log_printhex (p, buflen, ""); } } xfree (buffer); return 0; } /* Helper for the read_ef_foo functions to read the first record or * the entire data. */ static gpg_error_t read_first_record (app_t app, unsigned short fid, const char *fid_desc, unsigned char **r_buffer, size_t *r_buflen, int *r_use_read_record) { gpg_error_t err; int sw; *r_buffer = NULL; *r_buflen = 0; *r_use_read_record = 0; if (!fid) return gpg_error (GPG_ERR_NO_DATA); /* No such file. */ if (IS_CARDOS_5 (app)) { *r_use_read_record = 1; err = select_and_read_record (app, fid, 1, fid_desc, r_buffer, r_buflen, &sw); if (err && sw == SW_FILE_STRUCT) { *r_use_read_record = 0; err = select_and_read_binary (app, 0, fid_desc, r_buffer, r_buflen); } } else err = select_and_read_binary (app, fid, fid_desc, r_buffer, r_buflen); /* We get a not_found state in read_record mode if the select * succeeded but reading the record failed. Map that to no_data * which is what the caller of the read_ef_foo functions expect. */ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = gpg_error (GPG_ERR_NO_DATA); return err; } /* Parse the BIT STRING with the keyUsageFlags from the CommonKeyAttributes. */ static gpg_error_t parse_keyusage_flags (const unsigned char *der, size_t derlen, keyusage_flags_t *usageflags) { unsigned int bits, mask; int i, unused, full; memset (usageflags, 0, sizeof *usageflags); if (!derlen) return gpg_error (GPG_ERR_INV_OBJ); unused = *der++; derlen--; if ((!derlen && unused) || unused/8 > derlen) return gpg_error (GPG_ERR_ENCODING_PROBLEM); full = derlen - (unused+7)/8; unused %= 8; mask = 0; for (i=1; unused; i <<= 1, unused--) mask |= i; /* First octet */ if (derlen) { bits = *der++; derlen--; if (full) full--; else { bits &= ~mask; mask = 0; } } else bits = 0; if ((bits & 0x80)) usageflags->encrypt = 1; if ((bits & 0x40)) usageflags->decrypt = 1; if ((bits & 0x20)) usageflags->sign = 1; if ((bits & 0x10)) usageflags->sign_recover = 1; if ((bits & 0x08)) usageflags->wrap = 1; if ((bits & 0x04)) usageflags->unwrap = 1; if ((bits & 0x02)) usageflags->verify = 1; if ((bits & 0x01)) usageflags->verify_recover = 1; /* Second octet. */ if (derlen) { bits = *der++; derlen--; if (full) full--; else { bits &= ~mask; } } else bits = 0; if ((bits & 0x80)) usageflags->derive = 1; if ((bits & 0x40)) usageflags->non_repudiation = 1; return 0; } static void dump_keyusage_flags (keyusage_flags_t usageflags) { const char *s = ""; log_info ("p15: usage="); if (usageflags.encrypt) log_printf ("%sencrypt", s), s = ","; if (usageflags.decrypt) log_printf ("%sdecrypt", s), s = ","; if (usageflags.sign ) log_printf ("%ssign", s), s = ","; if (usageflags.sign_recover) log_printf ("%ssign_recover", s), s = ","; if (usageflags.wrap ) log_printf ("%swrap", s), s = ","; if (usageflags.unwrap ) log_printf ("%sunwrap", s), s = ","; if (usageflags.verify ) log_printf ("%sverify", s), s = ","; if (usageflags.verify_recover) log_printf ("%sverify_recover", s), s = ","; if (usageflags.derive ) log_printf ("%sderive", s), s = ","; if (usageflags.non_repudiation) log_printf ("%snon_repudiation", s), s = ","; } static void dump_keyaccess_flags (keyaccess_flags_t accessflags) { const char *s = ""; log_info ("p15: access="); if (accessflags.sensitive) log_printf ("%ssensitive", s), s = ","; if (accessflags.extractable) log_printf ("%sextractable", s), s = ","; if (accessflags.always_sensitive) log_printf ("%salways_sensitive", s), s = ","; if (accessflags.never_extractable) log_printf ("%snever_extractable", s), s = ","; if (accessflags.local) log_printf ("%slocal", s), s = ","; } static void dump_gpgusage_flags (gpgusage_flags_t gpgusage) { const char *s = ""; log_info ("p15: gpgusage="); if (gpgusage.cert) log_printf ("%scert", s), s = ","; if (gpgusage.sign) log_printf ("%ssign", s), s = ","; if (gpgusage.encr) log_printf ("%sencr", s), s = ","; if (gpgusage.auth) log_printf ("%sauth", s), s = ","; } /* Parse the BIT STRING with the keyAccessFlags from the CommonKeyAttributes. */ static gpg_error_t parse_keyaccess_flags (const unsigned char *der, size_t derlen, keyaccess_flags_t *accessflags) { unsigned int bits, mask; int i, unused, full; memset (accessflags, 0, sizeof *accessflags); if (!derlen) return gpg_error (GPG_ERR_INV_OBJ); unused = *der++; derlen--; if ((!derlen && unused) || unused/8 > derlen) return gpg_error (GPG_ERR_ENCODING_PROBLEM); full = derlen - (unused+7)/8; unused %= 8; mask = 0; for (i=1; unused; i <<= 1, unused--) mask |= i; /* First octet */ if (derlen) { bits = *der++; derlen--; if (full) full--; else { bits &= ~mask; mask = 0; } } else bits = 0; if ((bits & 0x10)) accessflags->local = 1; if ((bits & 0x08)) accessflags->never_extractable = 1; if ((bits & 0x04)) accessflags->always_sensitive = 1; if ((bits & 0x02)) accessflags->extractable = 1; if ((bits & 0x01)) accessflags->sensitive = 1; accessflags->any = 1; return 0; } /* Parse the commonObjectAttributes and store a malloced authid at * (r_authid,r_authidlen). (NULL,0) is stored on error or if no * authid is found. IF R_LABEL is not NULL the label is stored there * as a malloced string (spaces are replaced by underscores). * * Example data: * 2 30 17: SEQUENCE { -- commonObjectAttributes * 4 0C 8: UTF8String 'SK.CH.DS' -- label * 14 03 2: BIT STRING 6 unused bits * : '01'B (bit 0) * 18 04 1: OCTET STRING --authid * : 07 * : } */ static gpg_error_t parse_common_obj_attr (unsigned char const **buffer, size_t *size, unsigned char **r_authid, size_t *r_authidlen, char **r_label) { gpg_error_t err; int where; int class, tag, constructed, ndef; size_t objlen, hdrlen, nnn; const unsigned char *ppp; int ignore_eof = 0; char *p; *r_authid = NULL; *r_authidlen = 0; if (r_label) *r_label = NULL; where = __LINE__; err = parse_ber_header (buffer, size, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > *size || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; ppp = *buffer; nnn = objlen; *buffer += objlen; *size -= objlen; /* Search the optional AuthId. */ ignore_eof = 1; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; if (tag == TAG_UTF8_STRING) { if (r_label) { *r_label = xtrymalloc (objlen + 1); if (!*r_label) { err = gpg_error_from_syserror (); goto leave; } memcpy (*r_label, ppp, objlen); (*r_label)[objlen] = 0; /* We don't want spaces in the labels due to the properties * of CHV-LABEL. */ for (p = *r_label; *p; p++) if (ascii_isspace (*p)) *p = '_'; } ppp += objlen; nnn -= objlen; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; } if (tag == TAG_BIT_STRING) { ppp += objlen; /* Skip the CommonObjectFlags. */ nnn -= objlen; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; } if (tag == TAG_OCTET_STRING && objlen) { *r_authid = xtrymalloc (objlen); if (!*r_authid) { err = gpg_error_from_syserror (); goto leave; } memcpy (*r_authid, ppp, objlen); *r_authidlen = objlen; } leave: if (ignore_eof && gpg_err_code (err) == GPG_ERR_EOF) err = 0; else if (err) log_error ("p15: error parsing commonObjectAttributes at %d: %s\n", where, gpg_strerror (err)); if (err && r_label) { xfree (*r_label); *r_label = NULL; } return err; } /* Parse the commonKeyAttributes. On success store the objid at * (R_OBJID/R_OBJIDLEN), sets the key usage flags at USAGEFLAGS and * the optiona key refrence at R_KEY_REFERENCE. The latter is only * valid if true is also stored at R_KEY_REFERENCE_VALID. * * Example data: * * 21 30 12: SEQUENCE { -- commonKeyAttributes * 23 04 1: OCTET STRING * : 01 * 26 03 3: BIT STRING 6 unused bits * : '1000000000'B (bit 9) * 31 02 2: INTEGER 80 -- keyReference (optional) * : } */ static gpg_error_t parse_common_key_attr (unsigned char const **buffer, size_t *size, unsigned char **r_objid, size_t *r_objidlen, keyusage_flags_t *usageflags, keyaccess_flags_t *accessflags, unsigned long *r_key_reference, int *r_key_reference_valid) { gpg_error_t err; int where; int class, tag, constructed, ndef; size_t objlen, hdrlen, nnn; const unsigned char *ppp; int ignore_eof = 0; unsigned long ul; const unsigned char *objid = NULL; size_t objidlen; unsigned long key_reference = 0; int key_reference_valid = 0; *r_objid = NULL; *r_objidlen = 0; memset (usageflags, 0, sizeof *usageflags); memset (accessflags, 0, sizeof *accessflags); *r_key_reference_valid = 0; where = __LINE__; err = parse_ber_header (buffer, size, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > *size || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; ppp = *buffer; nnn = objlen; *buffer += objlen; *size -= objlen; /* Get the Id. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; objid = ppp; objidlen = objlen; ppp += objlen; nnn -= objlen; /* Get the KeyUsageFlags. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; err = parse_keyusage_flags (ppp, objlen, usageflags); if (err) goto leave; ppp += objlen; nnn -= objlen; ignore_eof = 1; /* Remaining items are optional. */ /* Find the keyReference */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN) { /* Skip the native element. */ ppp += objlen; nnn -= objlen; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; } if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING) { /* These are the keyAccessFlags. */ err = parse_keyaccess_flags (ppp, objlen, accessflags); if (err) goto leave; ppp += objlen; nnn -= objlen; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; } if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER) { /* This is the keyReference. */ for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*ppp++) & 0xff; nnn--; } key_reference = ul; key_reference_valid = 1; } leave: if (ignore_eof && gpg_err_code (err) == GPG_ERR_EOF) err = 0; if (!err) { if (!objid || !objidlen) err = gpg_error (GPG_ERR_INV_OBJ); else { *r_objid = xtrymalloc (objidlen); if (!*r_objid) err = gpg_error_from_syserror (); else { memcpy (*r_objid, objid, objidlen); *r_objidlen = objidlen; } } } if (!err && key_reference_valid) { *r_key_reference = key_reference; *r_key_reference_valid = 1; } if (err) log_error ("p15: error parsing commonKeyAttributes at %d: %s\n", where, gpg_strerror (err)); return err; } /* Read and parse the Private Key Directory Files. * * Sample object: * SEQUENCE { * SEQUENCE { -- commonObjectAttributes * UTF8String 'SK.CH.DS' * BIT STRING 6 unused bits * '01'B (bit 0) -- flags: non-modifiable,private * OCTET STRING --authid * 07 * } * SEQUENCE { -- commonKeyAttributes * OCTET STRING * 01 * BIT STRING 6 unused bits * '1000000000'B (bit 9) -- keyusage: non-repudiation * INTEGER 80 -- keyReference (optional) * } * [1] { -- keyAttributes * SEQUENCE { -- privateRSAKeyAttributes * SEQUENCE { -- objectValue * OCTET STRING --path * 3F 00 40 16 00 50 * } * INTEGER 1024 -- modulus * } * } * } * * Sample part for EC objects: * [1] { -- keyAttributes * [1] { -- privateECkeyAttributes * SEQUENCE { -- objectValue * SEQUENCE { --path * OCTET STRING 50 72 4B 03 * } * INTEGER 33 -- Not in PKCS#15v1.1, need to buy 7816-15? * } * } */ static gpg_error_t read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result) { gpg_error_t err; unsigned char *buffer; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; prkdf_object_t prkdflist = NULL; int i; int recno = 1; unsigned char *authid = NULL; size_t authidlen = 0; unsigned char *objid = NULL; size_t objidlen = 0; char *label = NULL; int record_mode; err = read_first_record (app, fid, "PrKDF", &buffer, &buflen, &record_mode); if (err) return err; p = buffer; n = buflen; /* Loop over the records. We stop as soon as we detect a new record starting with 0x00 or 0xff as these values are commonly used to pad data blocks and are no valid ASN.1 encoding. Note the special handling for record mode at the end of the loop. */ while (n && *p && *p != 0xff) { const unsigned char *pp; size_t nn; int where; const char *errstr = NULL; prkdf_object_t prkdf = NULL; unsigned long ul; keyusage_flags_t usageflags; keyaccess_flags_t accessflags; unsigned long key_reference = 0; int key_reference_valid = 0; int is_ecc = 0; where = __LINE__; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) ; else if (objlen > n) err = gpg_error (GPG_ERR_INV_OBJ); else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) ; /* PrivateRSAKeyAttributes */ else if (class == CLASS_CONTEXT) { switch (tag) { case 0: is_ecc = 1; break; /* PrivateECKeyAttributes */ case 1: errstr = "DH key objects are not supported"; break; case 2: errstr = "DSA key objects are not supported"; break; case 3: errstr = "KEA key objects are not supported"; break; default: errstr = "unknown privateKeyObject"; break; } if (errstr) goto parse_error; } else { err = gpg_error (GPG_ERR_INV_OBJ); goto parse_error; } if (err) { log_error ("p15: error parsing PrKDF record: %s\n", gpg_strerror (err)); goto leave; } pp = p; nn = objlen; p += objlen; n -= objlen; /* Parse the commonObjectAttributes. */ where = __LINE__; xfree (authid); xfree (label); err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label); if (err) goto parse_error; /* Parse the commonKeyAttributes. */ where = __LINE__; xfree (objid); err = parse_common_key_attr (&pp, &nn, &objid, &objidlen, &usageflags, &accessflags, &key_reference, &key_reference_valid); if (err) goto parse_error; log_assert (objid); /* Skip commonPrivateKeyAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class == CLASS_CONTEXT && tag == 0) { pp += objlen; nn -= objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); } /* Parse the keyAttributes. */ if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) ; else if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) ; /* A typeAttribute always starts with a sequence. */ else err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; /* Check that the reference is a Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) { errstr = "unsupported reference type"; goto parse_error; } nn = objlen; /* Parse the Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; /* Make sure that the next element has a path of even length * (FIDs are two bytes each). We should check that the path * length is non-zero but some cards return a zero length path * nevertheless (e.g. A.E.T. Europe Java applets). */ if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING || (objlen & 1) ) { errstr = "invalid path reference"; goto parse_error; } /* Create a new PrKDF list item. */ prkdf = xtrycalloc (1, (sizeof *prkdf - sizeof(unsigned short) + objlen/2 * sizeof(unsigned short))); if (!prkdf) { err = gpg_error_from_syserror (); goto leave; } prkdf->is_ecc = is_ecc; prkdf->objidlen = objidlen; prkdf->objid = objid; objid = NULL; if (authid) { prkdf->authidlen = authidlen; prkdf->authid = authid; authid = NULL; } if (label) { prkdf->label = label; label = NULL; } prkdf->pathlen = objlen/2; for (i=0; i < prkdf->pathlen; i++, pp += 2, nn -= 2) prkdf->path[i] = ((pp[0] << 8) | pp[1]); prkdf->usageflags = usageflags; prkdf->accessflags = accessflags; prkdf->key_reference = key_reference; prkdf->key_reference_valid = key_reference_valid; if (nn) { /* An index and length follows. */ prkdf->have_off = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } prkdf->off = ul; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 0)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } prkdf->len = ul; } /* The info is printed later in read_p15_info because we also * want to look at the certificates. */ /* Put it into the list. */ prkdf->next = prkdflist; prkdflist = prkdf; prkdf = NULL; goto next_record; /* Ready with this record. */ parse_error: log_error ("p15: error parsing PrKDF record at %d: %s - skipped\n", where, errstr? errstr : gpg_strerror (err)); if (prkdf) { xfree (prkdf->objid); xfree (prkdf->authid); xfree (prkdf->label); xfree (prkdf); } err = 0; next_record: /* If the card uses a record oriented file structure, read the * next record. Otherwise we keep on parsing the current buffer. */ recno++; if (record_mode) { xfree (buffer); buffer = NULL; err = select_and_read_record (app, 0, recno, "PrKDF", &buffer, &buflen, NULL); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = 0; goto leave; } p = buffer; n = buflen; } } /* End looping over all records. */ leave: xfree (authid); xfree (label); xfree (objid); xfree (buffer); if (err) release_prkdflist (prkdflist); else *result = prkdflist; return err; } /* Read and parse the Public Keys Directory File. */ static gpg_error_t read_ef_pukdf (app_t app, unsigned short fid, pukdf_object_t *result) { gpg_error_t err; unsigned char *buffer; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; pukdf_object_t pukdflist = NULL; int i; int recno = 1; unsigned char *authid = NULL; size_t authidlen = 0; unsigned char *objid = NULL; size_t objidlen = 0; char *label = NULL; int record_mode; err = read_first_record (app, fid, "PuKDF", &buffer, &buflen, &record_mode); if (err) return err; p = buffer; n = buflen; /* Loop over the records. We stop as soon as we detect a new record * starting with 0x00 or 0xff as these values are commonly used to * pad data blocks and are no valid ASN.1 encoding. Note the * special handling for record mode at the end of the loop. */ while (n && *p && *p != 0xff) { const unsigned char *pp; size_t nn; int where; const char *errstr = NULL; pukdf_object_t pukdf = NULL; unsigned long ul; keyusage_flags_t usageflags; keyaccess_flags_t accessflags; unsigned long key_reference = 0; int key_reference_valid = 0; where = __LINE__; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) ; else if (objlen > n) err = gpg_error (GPG_ERR_INV_OBJ); else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) ; /* PublicRSAKeyAttributes */ else if (class == CLASS_CONTEXT) { switch (tag) { case 0: break; /* EC key object */ case 1: errstr = "DH key objects are not supported"; break; case 2: errstr = "DSA key objects are not supported"; break; case 3: errstr = "KEA key objects are not supported"; break; default: errstr = "unknown publicKeyObject"; break; } if (errstr) goto parse_error; } else { err = gpg_error (GPG_ERR_INV_OBJ); goto parse_error; } if (err) { log_error ("p15: error parsing PuKDF record: %s\n", gpg_strerror (err)); goto leave; } pp = p; nn = objlen; p += objlen; n -= objlen; /* Parse the commonObjectAttributes. */ where = __LINE__; xfree (authid); xfree (label); err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label); if (err) goto parse_error; /* Parse the commonKeyAttributes. */ where = __LINE__; xfree (objid); err = parse_common_key_attr (&pp, &nn, &objid, &objidlen, &usageflags, &accessflags, &key_reference, &key_reference_valid); if (err) goto parse_error; log_assert (objid); /* Parse the subClassAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class == CLASS_CONTEXT && tag == 0) { /* Skip this CommonPublicKeyAttribute. */ pp += objlen; nn -= objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); } /* We expect a typeAttribute. */ if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; /* No typeAttribute. */ nn = objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) ; else if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) ; /* A typeAttribute always starts with a sequence. */ else err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; /* Check that the reference is a Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) { errstr = "unsupported reference type"; goto parse_error; } nn = objlen; /* Parse the Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; /* Make sure that the next element has a path of even length * (FIDs are two bytes each). */ if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING || (objlen & 1) ) { errstr = "invalid path reference"; goto parse_error; } /* Create a new PuKDF list item. */ pukdf = xtrycalloc (1, (sizeof *pukdf - sizeof(unsigned short) + objlen/2 * sizeof(unsigned short))); if (!pukdf) { err = gpg_error_from_syserror (); goto leave; } pukdf->objidlen = objidlen; pukdf->objid = objid; objid = NULL; if (authid) { pukdf->authidlen = authidlen; pukdf->authid = authid; authid = NULL; } if (label) { pukdf->label = label; label = NULL; } pukdf->pathlen = objlen/2; for (i=0; i < pukdf->pathlen; i++, pp += 2, nn -= 2) pukdf->path[i] = ((pp[0] << 8) | pp[1]); pukdf->usageflags = usageflags; pukdf->accessflags = accessflags; pukdf->key_reference = key_reference; pukdf->key_reference_valid = key_reference_valid; if (nn) { /* An index and length follows. */ pukdf->have_off = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } pukdf->off = ul; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 0)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } pukdf->len = ul; } if (opt.verbose) { log_info ("p15: PuKDF %04hX: id=", fid); for (i=0; i < pukdf->objidlen; i++) log_printf ("%02X", pukdf->objid[i]); if (pukdf->label) log_printf (" (%s)", pukdf->label); log_info ("p15: path="); for (i=0; i < pukdf->pathlen; i++) log_printf ("%s%04hX", i?"/":"",pukdf->path[i]); if (pukdf->have_off) log_printf ("[%lu/%lu]", pukdf->off, pukdf->len); if (pukdf->authid) { log_printf (" authid="); for (i=0; i < pukdf->authidlen; i++) log_printf ("%02X", pukdf->authid[i]); } if (pukdf->key_reference_valid) log_printf (" keyref=0x%02lX", pukdf->key_reference); if (pukdf->accessflags.any) dump_keyaccess_flags (pukdf->accessflags); dump_keyusage_flags (pukdf->usageflags); log_printf ("\n"); } /* Put it into the list. */ pukdf->next = pukdflist; pukdflist = pukdf; pukdf = NULL; goto next_record; /* Ready with this record. */ parse_error: log_error ("p15: error parsing PuKDF record at %d: %s - skipped\n", where, errstr? errstr : gpg_strerror (err)); if (pukdf) { xfree (pukdf->objid); xfree (pukdf->authid); xfree (pukdf->label); xfree (pukdf); } err = 0; next_record: /* If the card uses a record oriented file structure, read the * next record. Otherwise we keep on parsing the current buffer. */ recno++; if (record_mode) { xfree (buffer); buffer = NULL; err = select_and_read_record (app, 0, recno, "PuKDF", &buffer, &buflen, NULL); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = 0; goto leave; } p = buffer; n = buflen; } } /* End looping over all records. */ leave: xfree (authid); xfree (label); xfree (objid); xfree (buffer); if (err) release_pukdflist (pukdflist); else *result = pukdflist; return err; } /* Read and parse the Certificate Directory Files identified by FID. On success a newlist of CDF object gets stored at RESULT and the caller is then responsible of releasing this list. On error a error code is returned and RESULT won't get changed. */ static gpg_error_t read_ef_cdf (app_t app, unsigned short fid, int cdftype, cdf_object_t *result) { gpg_error_t err; unsigned char *buffer; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; cdf_object_t cdflist = NULL; int i; int recno = 1; unsigned char *authid = NULL; size_t authidlen = 0; char *label = NULL; int record_mode; err = read_first_record (app, fid, "CDF", &buffer, &buflen, &record_mode); if (err) return err; p = buffer; n = buflen; /* Loop over the records. We stop as soon as we detect a new record starting with 0x00 or 0xff as these values are commonly used to pad data blocks and are no valid ASN.1 encoding. Note the special handling for record mode at the end of the loop. */ while (n && *p && *p != 0xff) { const unsigned char *pp; size_t nn; int where; const char *errstr = NULL; cdf_object_t cdf = NULL; unsigned long ul; const unsigned char *objid; size_t objidlen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { log_error ("p15: error parsing CDF record: %s\n", gpg_strerror (err)); goto leave; } pp = p; nn = objlen; p += objlen; n -= objlen; /* Parse the commonObjectAttributes. */ where = __LINE__; xfree (authid); xfree (label); err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label); if (err) goto parse_error; /* Parse the commonCertificateAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; /* Get the Id. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; objid = ppp; objidlen = objlen; } /* Parse the certAttribute. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; /* Check that the reference is a Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) { errstr = "unsupported reference type"; goto parse_error; } nn = objlen; /* Parse the Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; /* Make sure that the next element has a path of even length * (FIDs are two bytes each). */ if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING || (objlen & 1) ) { errstr = "invalid path reference"; goto parse_error; } /* Create a new CDF list item. */ cdf = xtrycalloc (1, (sizeof *cdf - sizeof(unsigned short) + objlen/2 * sizeof(unsigned short))); if (!cdf) { err = gpg_error_from_syserror (); goto leave; } if (authid) { cdf->authidlen = authidlen; cdf->authid = authid; authid = NULL; } if (label) { cdf->label = label; label = NULL; } cdf->objidlen = objidlen; cdf->objid = xtrymalloc (objidlen); if (!cdf->objid) { err = gpg_error_from_syserror (); xfree (cdf); goto leave; } memcpy (cdf->objid, objid, objidlen); cdf->pathlen = objlen/2; for (i=0; i < cdf->pathlen; i++, pp += 2, nn -= 2) cdf->path[i] = ((pp[0] << 8) | pp[1]); if (nn) { /* An index and length follows. */ cdf->have_off = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } cdf->off = ul; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 0)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } cdf->len = ul; } if (opt.verbose) { log_info ("p15: CDF-%c %04hX: id=", cdftype, fid); for (i=0; i < cdf->objidlen; i++) log_printf ("%02X", cdf->objid[i]); if (cdf->label) log_printf (" (%s)", cdf->label); log_info ("p15: path="); for (i=0; i < cdf->pathlen; i++) log_printf ("%s%04hX", i?"/":"", cdf->path[i]); if (cdf->have_off) log_printf ("[%lu/%lu]", cdf->off, cdf->len); if (cdf->authid) { log_printf (" authid="); for (i=0; i < cdf->authidlen; i++) log_printf ("%02X", cdf->authid[i]); } log_printf ("\n"); } /* Put it into the list. */ cdf->next = cdflist; cdflist = cdf; cdf = NULL; goto next_record; /* Ready with this record. */ parse_error: log_error ("p15: error parsing CDF record at %d: %s - skipped\n", where, errstr? errstr : gpg_strerror (err)); xfree (cdf); err = 0; next_record: xfree (authid); xfree (label); /* If the card uses a record oriented file structure, read the * next record. Otherwise we keep on parsing the current buffer. */ recno++; if (record_mode) { xfree (buffer); buffer = NULL; err = select_and_read_record (app, 0, recno, "CDF", &buffer, &buflen, NULL); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = 0; goto leave; } p = buffer; n = buflen; } } /* End loop over all records. */ leave: xfree (authid); xfree (label); xfree (buffer); if (err) release_cdflist (cdflist); else *result = cdflist; return err; } /* * SEQUENCE { * SEQUENCE { -- CommonObjectAttributes * UTF8String 'specific PIN for DS' * BIT STRING 0 unused bits * '00000011'B * } * SEQUENCE { -- CommonAuthenticationObjectAttributes * OCTET STRING * 07 -- iD * } * * [1] { -- typeAttributes * SEQUENCE { -- PinAttributes * BIT STRING 0 unused bits * '0000100000110010'B -- local,initialized,needs-padding * -- exchangeRefData * ENUMERATED 1 -- ascii-numeric * INTEGER 6 -- minLength * INTEGER 6 -- storedLength * INTEGER 8 -- maxLength * [0] * 02 -- pinReference * GeneralizedTime 19/04/2002 12:12 GMT -- lastPinChange * SEQUENCE { * OCTET STRING * 3F 00 40 16 -- path to DF of PIN * } * } * } * } * * Or for an authKey: * * [1] { -- typeAttributes * SEQUENCE { -- AuthKeyAttributes * BOOLEAN TRUE -- derivedKey * OCTET STRING 02 -- authKeyId * } * } * } */ /* Read and parse an Authentication Object Directory File identified by FID. On success a newlist of AODF objects gets stored at RESULT and the caller is responsible of releasing this list. On error a error code is returned and RESULT won't get changed. */ static gpg_error_t read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result) { gpg_error_t err; unsigned char *buffer; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; aodf_object_t aodflist = NULL; int i; int recno = 1; int record_mode; err = read_first_record (app, fid, "AODF", &buffer, &buflen, &record_mode); if (err) return err; p = buffer; n = buflen; /* Loop over the records. We stop as soon as we detect a new record starting with 0x00 or 0xff as these values are commonly used to pad data blocks and are no valid ASN.1 encoding. Note the special handling for record mode at the end of the loop. */ while (n && *p && *p != 0xff) { const unsigned char *pp; size_t nn; int where; const char *errstr = NULL; auth_type_t auth_type; aodf_object_t aodf = NULL; unsigned long ul; const char *s; where = __LINE__; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) ; else if (objlen > n) err = gpg_error (GPG_ERR_INV_OBJ); else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) auth_type = AUTH_TYPE_PIN; /* PinAttributes */ else if (class == CLASS_CONTEXT && tag == 1 ) auth_type = AUTH_TYPE_AUTHKEY; /* AuthKeyAttributes */ else if (class == CLASS_CONTEXT) { switch (tag) { case 0: errstr = "biometric auth types are not supported"; break; case 2: errstr = "external auth type are not supported"; break; default: errstr = "unknown privateKeyObject"; break; } p += objlen; n -= objlen; goto parse_error; } else { err = gpg_error (GPG_ERR_INV_OBJ); goto parse_error; } if (err) { log_error ("p15: error parsing AODF record: %s\n", gpg_strerror (err)); goto leave; } pp = p; nn = objlen; p += objlen; n -= objlen; /* Allocate memory for a new AODF list item. */ aodf = xtrycalloc (1, sizeof *aodf); if (!aodf) goto no_core; aodf->fid = fid; aodf->auth_type = auth_type; /* Parse the commonObjectAttributes. */ where = __LINE__; err = parse_common_obj_attr (&pp, &nn, &aodf->authid, &aodf->authidlen, &aodf->label); if (err) goto parse_error; /* Parse the CommonAuthenticationObjectAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; /* Get the Id. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; aodf->objidlen = objlen; aodf->objid = xtrymalloc (objlen); if (!aodf->objid) goto no_core; memcpy (aodf->objid, ppp, objlen); } /* Parse the typeAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) ; else if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE) ; /* Okay */ else err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; if (auth_type == AUTH_TYPE_PIN) { /* PinFlags */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || !objlen || class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { unsigned int bits, mask; int unused, full; unused = *pp++; nn--; objlen--; if ((!objlen && unused) || unused/8 > objlen) { err = gpg_error (GPG_ERR_ENCODING_PROBLEM); goto parse_error; } full = objlen - (unused+7)/8; unused %= 8; mask = 0; for (i=1; unused; i <<= 1, unused--) mask |= i; /* The first octet */ bits = 0; if (objlen) { bits = *pp++; nn--; objlen--; if (full) full--; else { bits &= ~mask; mask = 0; } } if ((bits & 0x80)) /* ASN.1 bit 0. */ aodf->pinflags.case_sensitive = 1; if ((bits & 0x40)) /* ASN.1 bit 1. */ aodf->pinflags.local = 1; if ((bits & 0x20)) aodf->pinflags.change_disabled = 1; if ((bits & 0x10)) aodf->pinflags.unblock_disabled = 1; if ((bits & 0x08)) aodf->pinflags.initialized = 1; if ((bits & 0x04)) aodf->pinflags.needs_padding = 1; if ((bits & 0x02)) aodf->pinflags.unblocking_pin = 1; if ((bits & 0x01)) aodf->pinflags.so_pin = 1; /* The second octet. */ bits = 0; if (objlen) { bits = *pp++; nn--; objlen--; if (full) full--; else { bits &= ~mask; } } if ((bits & 0x80)) aodf->pinflags.disable_allowed = 1; if ((bits & 0x40)) aodf->pinflags.integrity_protected = 1; if ((bits & 0x20)) aodf->pinflags.confidentiality_protected = 1; if ((bits & 0x10)) aodf->pinflags.exchange_ref_data = 1; /* Skip remaining bits. */ pp += objlen; nn -= objlen; } /* PinType */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_ENUMERATED)) err = gpg_error (GPG_ERR_INV_OBJ); if (!err && objlen > sizeof (ul)) err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } aodf->pintype = ul; /* minLength */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (!err && objlen > sizeof (ul)) err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } aodf->min_length = ul; /* storedLength */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (!err && objlen > sizeof (ul)) err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } aodf->stored_length = ul; /* optional maxLength */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto ready; if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER) { if (objlen > sizeof (ul)) { err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); goto parse_error; } for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } aodf->max_length = ul; aodf->max_length_valid = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto ready; if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } /* Optional pinReference. */ if (class == CLASS_CONTEXT && tag == 0) { if (objlen > sizeof (ul)) { err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); goto parse_error; } for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*pp++) & 0xff; nn--; } aodf->pin_reference = ul; aodf->pin_reference_valid = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto ready; if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } /* Optional padChar. */ if (class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING) { if (objlen != 1) { errstr = "padChar is not of size(1)"; goto parse_error; } aodf->pad_char = *pp++; nn--; aodf->pad_char_valid = 1; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto ready; if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } /* Skip optional lastPinChange. */ if (class == CLASS_UNIVERSAL && tag == TAG_GENERALIZED_TIME) { pp += objlen; nn -= objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto ready; if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } /* Optional Path object. */ if (class == CLASS_UNIVERSAL || tag == TAG_SEQUENCE) { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; /* Make sure that the next element has a path of even * length (FIDs are two bytes each). */ if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING || (objlen & 1) ) { errstr = "invalid path reference"; goto parse_error; } aodf->pathlen = objlen/2; aodf->path = xtrycalloc (aodf->pathlen, sizeof *aodf->path); if (!aodf->path) goto no_core; for (i=0; i < aodf->pathlen; i++, ppp += 2, nnn -= 2) aodf->path[i] = ((ppp[0] << 8) | ppp[1]); if (nnn) { /* An index and length follows. */ aodf->have_off = 1; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*ppp++) & 0xff; nnn--; } aodf->off = ul; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_CONTEXT || tag != 0)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*ppp++) & 0xff; nnn--; } aodf->len = ul; } } } else if (auth_type == AUTH_TYPE_AUTHKEY) { } /* Ignore further objects which might be there due to future extensions of pkcs#15. */ ready: if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; if (opt.verbose) { log_info ("p15: AODF %04hX: id=", fid); for (i=0; i < aodf->objidlen; i++) log_printf ("%02X", aodf->objid[i]); if (aodf->label) log_printf (" (%s)", aodf->label); log_info ("p15: "); log_printf (" %s", aodf->auth_type == AUTH_TYPE_PIN? "pin" : aodf->auth_type == AUTH_TYPE_AUTHKEY? "authkey" : "?"); if (aodf->pathlen) { log_printf (" path="); for (i=0; i < aodf->pathlen; i++) log_printf ("%s%04hX", i?"/":"",aodf->path[i]); if (aodf->have_off) log_printf ("[%lu/%lu]", aodf->off, aodf->len); } if (aodf->authid) { log_printf (" authid="); for (i=0; i < aodf->authidlen; i++) log_printf ("%02X", aodf->authid[i]); } if (aodf->auth_type == AUTH_TYPE_PIN) { if (aodf->pin_reference_valid) log_printf (" pinref=0x%02lX", aodf->pin_reference); log_printf (" min=%lu", aodf->min_length); log_printf (" stored=%lu", aodf->stored_length); if (aodf->max_length_valid) log_printf (" max=%lu", aodf->max_length); if (aodf->pad_char_valid) log_printf (" pad=0x%02x", aodf->pad_char); log_info ("p15: flags="); s = ""; if (aodf->pinflags.case_sensitive) log_printf ("%scase_sensitive", s), s = ","; if (aodf->pinflags.local) log_printf ("%slocal", s), s = ","; if (aodf->pinflags.change_disabled) log_printf ("%schange_disabled", s), s = ","; if (aodf->pinflags.unblock_disabled) log_printf ("%sunblock_disabled", s), s = ","; if (aodf->pinflags.initialized) log_printf ("%sinitialized", s), s = ","; if (aodf->pinflags.needs_padding) log_printf ("%sneeds_padding", s), s = ","; if (aodf->pinflags.unblocking_pin) log_printf ("%sunblocking_pin", s), s = ","; if (aodf->pinflags.so_pin) log_printf ("%sso_pin", s), s = ","; if (aodf->pinflags.disable_allowed) log_printf ("%sdisable_allowed", s), s = ","; if (aodf->pinflags.integrity_protected) log_printf ("%sintegrity_protected", s), s = ","; if (aodf->pinflags.confidentiality_protected) log_printf ("%sconfidentiality_protected", s), s = ","; if (aodf->pinflags.exchange_ref_data) log_printf ("%sexchange_ref_data", s), s = ","; { char numbuf[50]; const char *s2; switch (aodf->pintype) { case PIN_TYPE_BCD: s2 = "bcd"; break; case PIN_TYPE_ASCII_NUMERIC: s2 = "ascii-numeric"; break; case PIN_TYPE_UTF8: s2 = "utf8"; break; case PIN_TYPE_HALF_NIBBLE_BCD: s2 = "half-nibble-bcd"; break; case PIN_TYPE_ISO9564_1: s2 = "iso9564-1"; break; default: sprintf (numbuf, "%lu", (unsigned long)aodf->pintype); s2 = numbuf; } log_printf ("%stype=%s", s, s2); s = ","; } } else if (aodf->auth_type == AUTH_TYPE_AUTHKEY) { } log_printf ("\n"); } /* Put it into the list. */ aodf->next = aodflist; aodflist = aodf; aodf = NULL; goto next_record; /* Ready with this record. */ no_core: err = gpg_error_from_syserror (); release_aodf_object (aodf); goto leave; parse_error: log_error ("p15: error parsing AODF record at %d: %s - skipped\n", where, errstr? errstr : gpg_strerror (err)); err = 0; release_aodf_object (aodf); next_record: /* If the card uses a record oriented file structure, read the * next record. Otherwise we keep on parsing the current buffer. */ recno++; if (record_mode) { xfree (buffer); buffer = NULL; err = select_and_read_record (app, 0, recno, "AODF", &buffer, &buflen, NULL); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = 0; goto leave; } p = buffer; n = buflen; } } /* End looping over all records. */ leave: xfree (buffer); if (err) release_aodflist (aodflist); else *result = aodflist; return err; } /* Print the BIT STRING with the tokenflags from the TokenInfo. */ static void print_tokeninfo_tokenflags (const unsigned char *der, size_t derlen) { unsigned int bits, mask; int i, unused, full; int other = 0; if (!derlen) { log_printf (" [invalid object]"); return; } unused = *der++; derlen--; if ((!derlen && unused) || unused/8 > derlen) { log_printf (" [wrong encoding]"); return; } full = derlen - (unused+7)/8; unused %= 8; mask = 0; for (i=1; unused; i <<= 1, unused--) mask |= i; /* First octet */ if (derlen) { bits = *der++; derlen--; if (full) full--; else { bits &= ~mask; mask = 0; } } else bits = 0; if ((bits & 0x80)) log_printf (" readonly"); if ((bits & 0x40)) log_printf (" loginRequired"); if ((bits & 0x20)) log_printf (" prnGeneration"); if ((bits & 0x10)) log_printf (" eidCompliant"); if ((bits & 0x08)) other = 1; if ((bits & 0x04)) other = 1; if ((bits & 0x02)) other = 1; if ((bits & 0x01)) other = 1; /* Next octet. */ if (derlen) other = 1; if (other) log_printf (" [unknown]"); } /* Read and parse the EF(TokenInfo). * * TokenInfo ::= SEQUENCE { * version INTEGER {v1(0)} (v1,...), * serialNumber OCTET STRING, * manufacturerID Label OPTIONAL, * label [0] Label OPTIONAL, * tokenflags TokenFlags, * seInfo SEQUENCE OF SecurityEnvironmentInfo OPTIONAL, * recordInfo [1] RecordInfo OPTIONAL, * supportedAlgorithms [2] SEQUENCE OF AlgorithmInfo OPTIONAL, * ..., * issuerId [3] Label OPTIONAL, * holderId [4] Label OPTIONAL, * lastUpdate [5] LastUpdate OPTIONAL, * preferredLanguage PrintableString OPTIONAL -- In accordance with * -- IETF RFC 1766 * } (CONSTRAINED BY { -- Each AlgorithmInfo.reference value must be unique --}) * * TokenFlags ::= BIT STRING { * readOnly (0), * loginRequired (1), * prnGeneration (2), * eidCompliant (3) * } * * * Sample EF 5032: * 30 31 02 01 00 04 04 05 45 36 9F 0C 0C 44 2D 54 01......E6...D-T * 72 75 73 74 20 47 6D 62 48 80 14 4F 66 66 69 63 rust GmbH..Offic * 65 20 69 64 65 6E 74 69 74 79 20 63 61 72 64 03 e identity card. * 02 00 40 20 63 61 72 64 03 02 00 40 00 00 00 00 ..@ card...@.... * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * * 0 49: SEQUENCE { * 2 1: INTEGER 0 * 5 4: OCTET STRING 05 45 36 9F * 11 12: UTF8String 'D-Trust GmbH' * 25 20: [0] 'Office identity card' * 47 2: BIT STRING * : '00000010'B (bit 1) * : Error: Spurious zero bits in bitstring. * : } */ static gpg_error_t read_ef_tokeninfo (app_t app) { gpg_error_t err; unsigned char *buffer = NULL; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; unsigned long ul; release_tokeninfo (app); app->app_local->card_product = CARD_PRODUCT_UNKNOWN; err = select_and_read_binary (app, 0x5032, "TokenInfo", &buffer, &buflen); if (err) return err; p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { log_error ("p15: error parsing TokenInfo: %s\n", gpg_strerror (err)); goto leave; } n = objlen; /* Version. */ err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_INTEGER)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*p++) & 0xff; n--; } if (ul) { log_error ("p15: invalid version %lu in TokenInfo\n", ul); err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } /* serialNumber. */ err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_OCTET_STRING || !objlen)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; xfree (app->app_local->serialno); app->app_local->serialno = xtrymalloc (objlen); if (!app->app_local->serialno) { err = gpg_error_from_syserror (); goto leave; } memcpy (app->app_local->serialno, p, objlen); app->app_local->serialnolen = objlen; p += objlen; n -= objlen; /* Is there an optional manufacturerID? */ err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || !objlen)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; if (class == CLASS_UNIVERSAL && tag == TAG_UTF8_STRING) { app->app_local->manufacturer_id = percent_data_escape (0, NULL, p, objlen); p += objlen; n -= objlen; /* Get next TLV. */ err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || !objlen)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; } if (class == CLASS_CONTEXT && tag == 0) { app->app_local->token_label = percent_data_escape (0, NULL, p, objlen); p += objlen; n -= objlen; /* Get next TLV. */ err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || !objlen)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto leave; } /* The next is the mandatory tokenflags object. */ if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING) { app->app_local->tokenflagslen = objlen; app->app_local->tokenflags = xtrymalloc (objlen); if (!app->app_local->tokenflags) { err = gpg_error_from_syserror (); goto leave; } memcpy (app->app_local->tokenflags, p, objlen); p += objlen; n -= objlen; } leave: xfree (buffer); return err; } /* Get all the basic information from the pkcs#15 card, check the structure and initialize our local context. This is used once at application initialization. */ static gpg_error_t read_p15_info (app_t app) { gpg_error_t err; prkdf_object_t prkdf; unsigned int flag; err = read_ef_tokeninfo (app); if (err) return err; /* If we don't have a serial number yet but the TokenInfo provides * one, use that. */ if (!APP_CARD(app)->serialno && app->app_local->serialno) { APP_CARD(app)->serialno = app->app_local->serialno; APP_CARD(app)->serialnolen = app->app_local->serialnolen; app->app_local->serialno = NULL; app->app_local->serialnolen = 0; err = app_munge_serialno (APP_CARD(app)); if (err) return err; } release_lists (app); if (IS_CARDOS_5 (app) && app->app_local->manufacturer_id && !ascii_strcasecmp (app->app_local->manufacturer_id, "GeNUA mbH")) { if (!app->app_local->card_product) app->app_local->card_product = CARD_PRODUCT_GENUA; } /* Read the ODF so that we know the location of all directory files. */ /* Fixme: We might need to get a non-standard ODF FID from TokenInfo. */ err = read_ef_odf (app, 0x5031); if (err) return err; /* Read certificate information. */ log_assert (!app->app_local->certificate_info); log_assert (!app->app_local->trusted_certificate_info); log_assert (!app->app_local->useful_certificate_info); err = read_ef_cdf (app, app->app_local->odf.certificates, 'c', &app->app_local->certificate_info); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) err = read_ef_cdf (app, app->app_local->odf.trusted_certificates, 't', &app->app_local->trusted_certificate_info); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) err = read_ef_cdf (app, app->app_local->odf.useful_certificates, 'u', &app->app_local->useful_certificate_info); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; if (err) return err; /* Read information about public keys. */ log_assert (!app->app_local->public_key_info); err = read_ef_pukdf (app, app->app_local->odf.public_keys, &app->app_local->public_key_info); if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA) err = read_ef_pukdf (app, app->app_local->odf.trusted_public_keys, &app->app_local->public_key_info); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; if (err) return err; /* Read information about private keys. */ log_assert (!app->app_local->private_key_info); err = read_ef_prkdf (app, app->app_local->odf.private_keys, &app->app_local->private_key_info); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; if (err) return err; /* Read information about authentication objects. */ log_assert (!app->app_local->auth_object_info); err = read_ef_aodf (app, app->app_local->odf.auth_objects, &app->app_local->auth_object_info); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; /* See whether we can extend the private key information using * information from certificates. We use only the first matching * certificate; if we want to change this strategy we should walk * over the certificates and then find the corresponsing private key * objects. */ app->app_local->any_gpgusage = 0; for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) { cdf_object_t cdf; char *extusage; char *p, *pend; int seen, i; if (opt.debug) log_printhex (prkdf->objid, prkdf->objidlen, "p15: prkdf id="); if (cdf_object_from_objid (app, prkdf->objidlen, prkdf->objid, &cdf) && cdf_object_from_label (app, prkdf->label, &cdf)) continue; /* No matching certificate. */ if (!cdf->cert) /* Read and parse the certificate. */ readcert_by_cdf (app, cdf, NULL, NULL); if (!cdf->cert) continue; /* Unsupported or broken certificate. */ if (prkdf->is_ecc) { const char *oid; const unsigned char *der; size_t off, derlen, objlen, hdrlen; int class, tag, constructed, ndef; for (i=0; !(err = ksba_cert_get_extension (cdf->cert, i, &oid, NULL, &off, &derlen)); i++) if (!strcmp (oid, "1.3.6.1.4.1.11591.2.2.10") ) break; if (!err && (der = ksba_cert_get_image (cdf->cert, NULL))) { der += off; err = parse_ber_header (&der, &derlen, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > derlen || tag != TAG_OCTET_STRING || ndef)) err = gpg_error (GPG_ERR_INV_OBJ); if (!err) { derlen = objlen; if (opt.debug) log_printhex (der, derlen, "p15: OpenPGP KDF parms:"); /* Store them if they match the known OpenPGP format. */ if (derlen == 4 && der[0] == 3 && der[1] == 1) memcpy (prkdf->ecdh_kdf, der, 4); } } err = 0; } if (ksba_cert_get_ext_key_usages (cdf->cert, &extusage)) continue; /* No extended key usage attribute. */ if (opt.debug) log_debug ("p15: ExtKeyUsages: %s\n", extusage); p = extusage; while (p && (pend=strchr (p, ':'))) { *pend++ = 0; if ( *pend == 'C' ) /* Look only at critical usages. */ { prkdf->extusage.valid = 1; seen = 1; if (!strcmp (p, oid_kp_codeSigning) || !strcmp (p, oid_kp_timeStamping) || !strcmp (p, oid_kp_ocspSigning) || !strcmp (p, oid_kp_ms_documentSigning) || !strcmp (p, oid_kp_ms_old_documentSigning)) prkdf->extusage.sign = 1; else if (!strcmp (p, oid_kp_emailProtection)) prkdf->extusage.encr = 1; else if (!strcmp (p, oid_kp_serverAuth) || !strcmp (p, oid_kp_clientAuth) || !strcmp (p, oid_kp_ms_smartcardLogon)) prkdf->extusage.auth = 1; else if (!strcmp (p, oid_kp_anyExtendedKeyUsage)) { prkdf->extusage.sign = 1; prkdf->extusage.encr = 1; prkdf->extusage.auth = 1; } else seen = 0; } else seen = 0; /* Now check the gpg Usage. Here we don't care about * critical or non-critical here. */ if (seen) ; /* No more need to look for other caps. */ else if (!strcmp (p, oid_kp_gpgUsageCert)) { prkdf->gpgusage.cert = 1; prkdf->gpgusage.any = 1; app->app_local->any_gpgusage = 1; } else if (!strcmp (p, oid_kp_gpgUsageSign)) { prkdf->gpgusage.sign = 1; prkdf->gpgusage.any = 1; app->app_local->any_gpgusage = 1; } else if (!strcmp (p, oid_kp_gpgUsageEncr)) { prkdf->gpgusage.encr = 1; prkdf->gpgusage.any = 1; app->app_local->any_gpgusage = 1; } else if (!strcmp (p, oid_kp_gpgUsageAuth)) { prkdf->gpgusage.auth = 1; prkdf->gpgusage.any = 1; app->app_local->any_gpgusage = 1; } /* Skip to next item. */ if ((p = strchr (pend, '\n'))) p++; } xfree (extusage); } /* See whether we can figure out something about the card. */ if (!app->app_local->card_product && app->app_local->manufacturer_id && !strcmp (app->app_local->manufacturer_id, "www.atos.net/cardos") && IS_CARDOS_5 (app)) { /* This is a modern CARDOS card. */ flag = 0; for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) { if (prkdf->label && !strcmp (prkdf->label, "IdentityKey") && prkdf->key_reference_valid && prkdf->key_reference == 1 && !prkdf->authid) flag |= 1; else if (prkdf->label && !strcmp (prkdf->label, "TransportKey") && prkdf->key_reference_valid && prkdf->key_reference==2 && prkdf->authid) flag |= 2; } if (flag == 3) app->app_local->card_product = CARD_PRODUCT_RSCS; } if (!app->app_local->card_product && app->app_local->token_label && !strncmp (app->app_local->token_label, "D-TRUST Card V3", 15) && app->app_local->card_type == CARD_TYPE_CARDOS_50) { app->app_local->card_product = CARD_PRODUCT_DTRUST; } /* Now print the info about the PrKDF. */ if (opt.verbose) { int i; unsigned char *atr; size_t atrlen; const char *cardstr; for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) { log_info ("p15: PrKDF %04hX: id=", app->app_local->odf.private_keys); for (i=0; i < prkdf->objidlen; i++) log_printf ("%02X", prkdf->objid[i]); if (prkdf->label) log_printf (" (%s)", prkdf->label); log_info ("p15: path="); for (i=0; i < prkdf->pathlen; i++) log_printf ("%s%04hX", i?"/":"",prkdf->path[i]); if (prkdf->have_off) log_printf ("[%lu/%lu]", prkdf->off, prkdf->len); if (prkdf->authid) { log_printf (" authid="); for (i=0; i < prkdf->authidlen; i++) log_printf ("%02X", prkdf->authid[i]); } if (prkdf->key_reference_valid) log_printf (" keyref=0x%02lX", prkdf->key_reference); log_printf (" type=%s", prkdf->is_ecc? "ecc":"rsa"); if (prkdf->accessflags.any) dump_keyaccess_flags (prkdf->accessflags); dump_keyusage_flags (prkdf->usageflags); if (prkdf->extusage.valid) log_info ("p15: extusage=%s%s%s%s%s", prkdf->extusage.sign? "sign":"", (prkdf->extusage.sign && prkdf->extusage.encr)?",":"", prkdf->extusage.encr? "encr":"", ((prkdf->extusage.sign || prkdf->extusage.encr) && prkdf->extusage.auth)?",":"", prkdf->extusage.auth? "auth":""); if (prkdf->gpgusage.any) dump_gpgusage_flags (prkdf->gpgusage); log_printf ("\n"); } log_info ("p15: TokenInfo:\n"); if (app->app_local->serialno) { log_info ("p15: serialNumber .: "); log_printhex (app->app_local->serialno, app->app_local->serialnolen, ""); } else if (APP_CARD(app)->serialno) { log_info ("p15: serialNumber .: "); log_printhex (APP_CARD(app)->serialno, APP_CARD(app)->serialnolen, ""); } if (app->app_local->manufacturer_id) log_info ("p15: manufacturerID: %s\n", app->app_local->manufacturer_id); if (app->app_local->card_product) { cardstr = cardproduct2str (app->app_local->card_product); log_info ("p15: product ......: %d%s%s%s\n", app->app_local->card_product, *cardstr? " (":"", cardstr, *cardstr? ")":""); } if (app->app_local->token_label) log_info ("p15: label ........: %s\n", app->app_local->token_label); if (app->app_local->tokenflags) { log_info ("p15: tokenflags ...:"); print_tokeninfo_tokenflags (app->app_local->tokenflags, app->app_local->tokenflagslen); log_printf ("\n"); } atr = apdu_get_atr (app_get_slot (app), &atrlen); log_info ("p15: atr ..........: "); if (!atr) log_printf ("[error]\n"); else { log_printhex (atr, atrlen, ""); xfree (atr); } cardstr = cardtype2str (app->app_local->card_type); log_info ("p15: cardtype .....: %d%s%s%s\n", app->app_local->card_type, *cardstr? " (":"", cardstr, *cardstr? ")":""); } return err; } /* Helper to do_learn_status: Send information about all certificates listed in CERTINFO back. Use CERTTYPE as type of the certificate. */ static gpg_error_t send_certinfo (app_t app, ctrl_t ctrl, const char *certtype, cdf_object_t certinfo) { for (; certinfo; certinfo = certinfo->next) { char *buf, *p; const char *label; char *labelbuf; buf = xtrymalloc (9 + certinfo->objidlen*2 + 1); if (!buf) return gpg_error_from_syserror (); p = stpcpy (buf, "P15"); if (app->app_local->home_df != DEFAULT_HOME_DF) { snprintf (p, 6, "-%04X", (unsigned int)(app->app_local->home_df & 0xffff)); p += 5; } p = stpcpy (p, "."); bin2hex (certinfo->objid, certinfo->objidlen, p); label = (certinfo->label && *certinfo->label)? certinfo->label : "-"; labelbuf = percent_data_escape (0, NULL, label, strlen (label)); if (!labelbuf) { xfree (buf); return gpg_error_from_syserror (); } send_status_info (ctrl, "CERTINFO", certtype, strlen (certtype), buf, strlen (buf), labelbuf, strlen (labelbuf), NULL, (size_t)0); xfree (buf); xfree (labelbuf); } return 0; } /* Get the keygrip of the private key object PRKDF. On success the * keygrip, the algo and the length are stored in the KEYGRIP, * KEYALGO, and KEYNBITS fields of the PRKDF object. */ static gpg_error_t keygrip_from_prkdf (app_t app, prkdf_object_t prkdf) { gpg_error_t err; cdf_object_t cdf; unsigned char *der; size_t derlen; ksba_cert_t cert; gcry_sexp_t s_pkey = NULL; /* Easy if we got a cached version. */ if (prkdf->keygrip_valid) return 0; xfree (prkdf->common_name); prkdf->common_name = NULL; xfree (prkdf->serial_number); prkdf->serial_number = NULL; /* We could have also checked whether a public key directory file * and a matching public key for PRKDF is available. This would * make extraction of the key faster. However, this way we don't * have a way to look at extended key attributes to check gpgusage. * FIXME: Add public key lookup if no certificate was found. */ /* Look for a matching certificate. A certificate matches if the id * matches the one of the private key info. If none was found we * also try to match on the label. */ err = cdf_object_from_objid (app, prkdf->objidlen, prkdf->objid, &cdf); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) err = cdf_object_from_label (app, prkdf->label, &cdf); if (!err && !cdf) err = gpg_error (GPG_ERR_NOT_FOUND); if (err) goto leave; err = readcert_by_cdf (app, cdf, &der, &derlen); if (err) goto leave; err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, der, derlen); xfree (der); if (!err) err = app_help_get_keygrip_string (cert, prkdf->keygrip, &s_pkey, NULL); if (!err && !prkdf->gpgusage.any) { /* Try to get the CN and the SerialNumber from the certificate; * we use a very simple approach here which should work in many * cases. Eventually we should add a rfc-2253 parser into * libksba to make it easier to parse such a string. * We don't do this if this is marked as gpg key and thus * has only a dummy certificate. * * First example string: * "CN=Otto Schily,O=Miniluv,C=DE" * Second example string: * "2.5.4.5=#445452323030303236333531,2.5.4.4=#4B6F6368," * "2.5.4.42=#5765726E6572,CN=Werner Koch,OU=For testing" * " purposes only!,O=Testorganisation,C=DE" */ char *dn = ksba_cert_get_subject (cert, 0); if (dn) { char *p, *pend, *buf; p = strstr (dn, "CN="); if (p && (p==dn || p[-1] == ',')) { p += 3; if (!(pend = strchr (p, ','))) pend = p + strlen (p); if (pend && pend > p && (prkdf->common_name = xtrymalloc ((pend - p) + 1))) { memcpy (prkdf->common_name, p, pend-p); prkdf->common_name[pend-p] = 0; } } p = strstr (dn, "2.5.4.5=#"); /* OID of the SerialNumber */ if (p && (p==dn || p[-1] == ',')) { p += 9; if (!(pend = strchr (p, ','))) pend = p + strlen (p); if (pend && pend > p && (buf = xtrymalloc ((pend - p) + 1))) { memcpy (buf, p, pend-p); buf[pend-p] = 0; if (!hex2str (buf, buf, strlen (buf)+1, NULL)) xfree (buf); /* Invalid hex encoding. */ else prkdf->serial_number = buf; } } ksba_free (dn); } } if (!err && !prkdf->keytime) { ksba_isotime_t isot; time_t t; ksba_cert_get_validity (cert, 0, isot); t = isotime2epoch (isot); prkdf->keytime = (t == (time_t)(-1))? 0 : (u32)t; prkdf->have_keytime = 1; } if (!err && !prkdf->keyalgostr) prkdf->keyalgostr = pubkey_algo_string (s_pkey, NULL); ksba_cert_release (cert); if (err) goto leave; prkdf->keyalgo = get_pk_algo_from_key (s_pkey); if (!prkdf->keyalgo) { err = gpg_error (GPG_ERR_PUBKEY_ALGO); goto leave; } prkdf->keynbits = gcry_pk_get_nbits (s_pkey); if (!prkdf->keynbits) { err = gpg_error (GPG_ERR_PUBKEY_ALGO); goto leave; } prkdf->keygrip_valid = 1; /* Yeah, got everything. */ leave: gcry_sexp_release (s_pkey); return err; } /* Return a malloced keyref string for PRKDF. Returns NULL on * malloc failure. */ static char * keyref_from_prkdf (app_t app, prkdf_object_t prkdf) { char *buf, *p; buf = xtrymalloc (4 + 5 + prkdf->objidlen*2 + 1); if (!buf) return NULL; p = stpcpy (buf, "P15"); if (app->app_local->home_df != DEFAULT_HOME_DF) { snprintf (p, 6, "-%04X", (unsigned int)(app->app_local->home_df & 0xffff)); p += 5; } p = stpcpy (p, "."); bin2hex (prkdf->objid, prkdf->objidlen, p); return buf; } static void set_usage_string (char usage[5], prkdf_object_t prkdf) { size_t usagelen = 0; if (prkdf->gpgusage.any) { if (prkdf->gpgusage.sign) usage[usagelen++] = 's'; if (prkdf->gpgusage.cert) usage[usagelen++] = 'c'; if (prkdf->gpgusage.encr) usage[usagelen++] = 'e'; if (prkdf->gpgusage.auth) usage[usagelen++] = 'a'; } else { if ((prkdf->usageflags.sign || prkdf->usageflags.sign_recover || prkdf->usageflags.non_repudiation) && (!prkdf->extusage.valid || prkdf->extusage.sign)) usage[usagelen++] = 's'; if ((prkdf->usageflags.sign || prkdf->usageflags.sign_recover) && (!prkdf->extusage.valid || prkdf->extusage.sign)) usage[usagelen++] = 'c'; if ((prkdf->usageflags.decrypt || prkdf->usageflags.unwrap) && (!prkdf->extusage.valid || prkdf->extusage.encr)) usage[usagelen++] = 'e'; if ((prkdf->usageflags.sign || prkdf->usageflags.sign_recover) && (!prkdf->extusage.valid || prkdf->extusage.auth)) usage[usagelen++] = 'a'; } if (!usagelen) usage[usagelen++] = '-'; usage[usagelen++] = 0; } /* Helper to do_learn_status: Send information about all known keypairs back. FIXME: much code duplication from send_certinfo(). */ static gpg_error_t send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t prkdf) { gpg_error_t err; for (; prkdf; prkdf = prkdf->next) { char *buf; int j; buf = keyref_from_prkdf (app, prkdf); if (!buf) return gpg_error_from_syserror (); err = keygrip_from_prkdf (app, prkdf); if (err) { log_error ("p15: error getting keygrip from "); for (j=0; j < prkdf->pathlen; j++) log_printf ("%s%04hX", j?"/":"", prkdf->path[j]); log_printf (": %s\n", gpg_strerror (err)); } else { char usage[5]; char keytime[20]; const char *algostr; log_assert (strlen (prkdf->keygrip) == 40); if (prkdf->keytime && prkdf->have_keytime) snprintf (keytime, sizeof keytime, "%lu", (unsigned long)prkdf->keytime); else strcpy (keytime, "-"); algostr = prkdf->keyalgostr; set_usage_string (usage, prkdf); send_status_info (ctrl, "KEYPAIRINFO", prkdf->keygrip, 2*KEYGRIP_LEN, buf, strlen (buf), usage, strlen (usage), keytime, strlen (keytime), algostr, strlen (algostr?algostr:""), NULL, (size_t)0); } xfree (buf); } return 0; } /* This is the handler for the LEARN command. Note that if * APP_LEARN_FLAG_REREAD is set and this function returns an error, * the caller must deinitialize this application. */ static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) { gpg_error_t err; if (flags & APP_LEARN_FLAG_REREAD) { err = read_p15_info (app); if (err) return err; } if ((flags & APP_LEARN_FLAG_KEYPAIRINFO)) err = 0; else { err = do_getattr (app, ctrl, "MANUFACTURER"); if (!err) err = send_certinfo (app, ctrl, "100", app->app_local->certificate_info); if (!err) err = send_certinfo (app, ctrl, "101", app->app_local->trusted_certificate_info); if (!err) err = send_certinfo (app, ctrl, "102", app->app_local->useful_certificate_info); } if (!err) err = send_keypairinfo (app, ctrl, app->app_local->private_key_info); if (!err) err = do_getattr (app, ctrl, "CHV-STATUS"); if (!err) err = do_getattr (app, ctrl, "CHV-LABEL"); return err; } /* Read a certificate using the information in CDF and return the * certificate in a newly malloced buffer R_CERT and its length * R_CERTLEN. Also parses the certificate. R_CERT and R_CERTLEN may * be NULL to do just the caching. */ static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf, unsigned char **r_cert, size_t *r_certlen) { gpg_error_t err; unsigned char *buffer = NULL; const unsigned char *p, *save_p; size_t buflen, n; int class, tag, constructed, ndef; size_t totobjlen, objlen, hdrlen; int rootca; int i; if (r_cert) *r_cert = NULL; if (r_certlen) *r_certlen = 0; /* First check whether it has been cached. */ if (cdf->cert) { const unsigned char *image; size_t imagelen; if (!r_cert || !r_certlen) return 0; /* Caller does not actually want the result. */ image = ksba_cert_get_image (cdf->cert, &imagelen); if (!image) { log_error ("p15: ksba_cert_get_image failed\n"); return gpg_error (GPG_ERR_INTERNAL); } *r_cert = xtrymalloc (imagelen); if (!*r_cert) return gpg_error_from_syserror (); memcpy (*r_cert, image, imagelen); *r_certlen = imagelen; return 0; } if (DBG_CARD) { log_info ("p15: Reading CDF: id="); for (i=0; i < cdf->objidlen; i++) log_printf ("%02X", cdf->objid[i]); if (cdf->label) log_printf (" (%s)", cdf->label); log_info ("p15: path="); for (i=0; i < cdf->pathlen; i++) log_printf ("%s%04hX", i?"/":"", cdf->path[i]); if (cdf->have_off) log_printf ("[%lu/%lu]", cdf->off, cdf->len); if (cdf->authid) { log_printf (" authid="); for (i=0; i < cdf->authidlen; i++) log_printf ("%02X", cdf->authid[i]); } log_printf ("\n"); } /* Read the entire file. fixme: This could be optimized by first reading the header to figure out how long the certificate actually is. */ err = select_ef_by_path (app, cdf->path, cdf->pathlen); if (err) goto leave; if (app->app_local->no_extended_mode || !cdf->len) err = iso7816_read_binary_ext (app_get_slot (app), 0, cdf->off, 0, &buffer, &buflen, NULL); else err = iso7816_read_binary_ext (app_get_slot (app), 1, cdf->off, cdf->len, &buffer, &buflen, NULL); if (!err && (!buflen || *buffer == 0xff)) err = gpg_error (GPG_ERR_NOT_FOUND); if (err) { log_error ("p15: error reading certificate id="); for (i=0; i < cdf->objidlen; i++) log_printf ("%02X", cdf->objid[i]); log_printf (" at "); for (i=0; i < cdf->pathlen; i++) log_printf ("%s%04hX", i? "/":"", cdf->path[i]); log_printf (": %s\n", gpg_strerror (err)); goto leave; } /* Check whether this is really a certificate. */ p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) rootca = 0; else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) rootca = 1; else { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } totobjlen = objlen + hdrlen; log_assert (totobjlen <= buflen); err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if (!rootca && class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) { /* The certificate seems to be contained in a userCertificate container. Skip this and assume the following sequence is the certificate. */ if (n < objlen) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } p += objlen; n -= objlen; save_p = p; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) ) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } totobjlen = objlen + hdrlen; log_assert (save_p + totobjlen <= buffer + buflen); memmove (buffer, save_p, totobjlen); } /* Try to parse and cache the certificate. */ err = ksba_cert_new (&cdf->cert); if (!err) { err = ksba_cert_init_from_mem (cdf->cert, buffer, totobjlen); if (!err) /* Call us to use the just cached cert object. */ err = readcert_by_cdf (app, cdf, r_cert, r_certlen); if (err) { ksba_cert_release (cdf->cert); cdf->cert = NULL; } } if (err) { log_error ("p15: caching certificate failed: %s\n", gpg_strerror (err)); /* We return the certificate anyway so that the caller has a * chance to get an even unsupported or broken certificate. */ if (r_cert && r_certlen) { *r_cert = buffer; buffer = NULL; *r_certlen = totobjlen; } } leave: xfree (buffer); return err; } /* Handler for the READCERT command. Read the certificate with id CERTID (as returned by learn_status in the CERTINFO status lines) and return it in the freshly allocated buffer to be stored at R_CERT and its length at R_CERTLEN. A error code will be returned on failure and R_CERT and R_CERTLEN will be set to (NULL,0). */ static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **r_cert, size_t *r_certlen) { gpg_error_t err; cdf_object_t cdf; *r_cert = NULL; *r_certlen = 0; err = cdf_object_from_certid (app, certid, &cdf); if (!err) err = readcert_by_cdf (app, cdf, r_cert, r_certlen); return err; } /* Sort helper for an array of authentication objects. */ static int compare_aodf_objid (const void *arg_a, const void *arg_b) { const aodf_object_t a = *(const aodf_object_t *)arg_a; const aodf_object_t b = *(const aodf_object_t *)arg_b; int rc; rc = memcmp (a->objid, b->objid, a->objidlen < b->objidlen? a->objidlen : b->objidlen); if (!rc) { if (a->objidlen < b->objidlen) rc = -1; else if (a->objidlen > b->objidlen) rc = 1; } return rc; } static void send_key_fpr_line (ctrl_t ctrl, int number, const unsigned char *fpr) { char buf[41]; char numbuf[25]; bin2hex (fpr, 20, buf); if (number == -1) *numbuf = 0; /* Don't print the key number */ else snprintf (numbuf, sizeof numbuf, "%d", number); send_status_info (ctrl, "KEY-FPR", numbuf, (size_t)strlen(numbuf), buf, (size_t)strlen (buf), NULL, 0); } /* If possible emit a FPR-KEY status line for the private key object * PRKDF using NUMBER as index. */ static void send_key_fpr (app_t app, ctrl_t ctrl, prkdf_object_t prkdf, int number) { gpg_error_t err; cdf_object_t cdf; unsigned char *pk, *fixed_pk; size_t pklen, fixed_pklen; const unsigned char *m, *e, *q; size_t mlen, elen, qlen; unsigned char fpr20[20]; if (cdf_object_from_objid (app, prkdf->objidlen, prkdf->objid, &cdf) && cdf_object_from_label (app, prkdf->label, &cdf)) return; if (!cdf->cert) readcert_by_cdf (app, cdf, NULL, NULL); if (!cdf->cert) return; if (!prkdf->have_keytime) return; pk = ksba_cert_get_public_key (cdf->cert); if (!pk) return; pklen = gcry_sexp_canon_len (pk, 0, NULL, &err); if (uncompress_ecc_q_in_canon_sexp (pk, pklen, &fixed_pk, &fixed_pklen)) { xfree (pk); return; } if (fixed_pk) { xfree (pk); pk = NULL; pk = fixed_pk; pklen = fixed_pklen; } switch (prkdf->keyalgo) { case GCRY_PK_RSA: if (!get_rsa_pk_from_canon_sexp (pk, pklen, &m, &mlen, &e, &elen) && !compute_openpgp_fpr_rsa (4, prkdf->keytime, m, mlen, e, elen, fpr20, NULL)) send_key_fpr_line (ctrl, number, fpr20); break; case GCRY_PK_ECC: case GCRY_PK_ECDSA: case GCRY_PK_ECDH: case GCRY_PK_EDDSA: /* Note that NUMBER 2 indicates the encryption key. */ if (!get_ecc_q_from_canon_sexp (pk, pklen, &q, &qlen) && !compute_openpgp_fpr_ecc (4, prkdf->keytime, prkdf->keyalgostr, number == 2, q, qlen, prkdf->ecdh_kdf, 4, fpr20, NULL)) send_key_fpr_line (ctrl, number, fpr20); break; default: /* No Fingerprint for an unknown algo. */ break; } xfree (pk); } /* Implement the GETATTR command. This is similar to the LEARN command but returns just one value via the status interface. */ static gpg_error_t do_getattr (app_t app, ctrl_t ctrl, const char *name) { gpg_error_t err; prkdf_object_t prkdf; if (!strcmp (name, "$AUTHKEYID") || !strcmp (name, "$ENCRKEYID") || !strcmp (name, "$SIGNKEYID")) { char *buf; /* We return the ID of the first private key capable of the * requested action. If any gpgusage flag has been set for the * card we consult the gpgusage flags and not the regualr usage * flags. */ /* FIXME: This changed: Note that we do not yet return * non_repudiation keys for $SIGNKEYID because our D-Trust * testcard uses rsaPSS, which is not supported by gpgsm and not * covered by the VS-NfD approval. */ for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) { if (app->app_local->any_gpgusage) { if ((name[1] == 'A' && prkdf->gpgusage.auth) || (name[1] == 'E' && prkdf->gpgusage.encr) || (name[1] == 'S' && prkdf->gpgusage.sign)) break; } else { if ((name[1] == 'A' && (prkdf->usageflags.sign || prkdf->usageflags.sign_recover)) || (name[1] == 'E' && (prkdf->usageflags.decrypt || prkdf->usageflags.unwrap)) || (name[1] == 'S' && (prkdf->usageflags.sign || prkdf->usageflags.sign_recover))) break; } } if (prkdf) { buf = keyref_from_prkdf (app, prkdf); if (!buf) return gpg_error_from_syserror (); send_status_info (ctrl, name, buf, strlen (buf), NULL, 0); xfree (buf); } return 0; } else if (!strcmp (name, "$DISPSERIALNO")) { /* For certain cards we return special IDs. There is no general rule for it so we need to decide case by case. */ if (app->app_local->card_type == CARD_TYPE_BELPIC) { /* The eID card has a card number printed on the front matter which seems to be a good indication. */ unsigned char *buffer; const unsigned char *p; size_t buflen, n; unsigned short path[] = { 0x3F00, 0xDF01, 0x4031 }; err = select_ef_by_path (app, path, DIM(path) ); if (!err) err = iso7816_read_binary (app_get_slot (app), 0, 0, &buffer, &buflen); if (err) { log_error ("p15: error accessing EF(ID): %s\n", gpg_strerror (err)); return err; } p = find_tlv (buffer, buflen, 1, &n); if (p && n == 12) { char tmp[12+2+1]; memcpy (tmp, p, 3); tmp[3] = '-'; memcpy (tmp+4, p+3, 7); tmp[11] = '-'; memcpy (tmp+12, p+10, 2); tmp[14] = 0; send_status_info (ctrl, name, tmp, strlen (tmp), NULL, 0); xfree (buffer); return 0; } xfree (buffer); } else { char *sn; /* We use the first private key object which has a serial * number set. If none was found, we parse the first * object and see whether this has then a serial number. */ for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) if (prkdf->serial_number) break; if (!prkdf && app->app_local->private_key_info) { prkdf = app->app_local->private_key_info; keygrip_from_prkdf (app, prkdf); if (!prkdf->serial_number) prkdf = NULL; } sn = get_dispserialno (app, prkdf); /* Unless there is a bogus S/N in the cert, or the product * has a different strategy for the display-s/n, we should * have a suitable one from the cert now. */ if (sn) { err = send_status_printf (ctrl, name, "%s", sn); xfree (sn); return err; } } /* No abbreviated serial number. */ } else if (!strcmp (name, "MANUFACTURER")) { if (app->app_local->manufacturer_id && !strchr (app->app_local->manufacturer_id, '[') && app->app_local->card_product) return send_status_printf (ctrl, "MANUFACTURER", "0 %s [%s]", app->app_local->manufacturer_id, cardproduct2str (app->app_local->card_product)); else if (app->app_local->manufacturer_id) return send_status_printf (ctrl, "MANUFACTURER", "0 %s", app->app_local->manufacturer_id); else return 0; } else if (!strcmp (name, "CHV-STATUS") || !strcmp (name, "CHV-LABEL")) { int is_label = (name[4] == 'L'); aodf_object_t aodf; aodf_object_t aodfarray[16]; int naodf = 0; membuf_t mb; char *p; int i; /* Put the AODFs into an array for easier sorting. Note that we * handle onl the first 16 encountrer which should be more than * enough. */ for (aodf = app->app_local->auth_object_info; aodf && naodf < DIM(aodfarray); aodf = aodf->next) if (aodf->objidlen && aodf->pin_reference_valid) aodfarray[naodf++] = aodf; qsort (aodfarray, naodf, sizeof *aodfarray, compare_aodf_objid); init_membuf (&mb, 256); for (i = 0; i < naodf; i++) { /* int j; */ /* log_debug ("p15: AODF[%d] pinref=%lu id=", */ /* i, aodfarray[i]->pin_reference); */ /* for (j=0; j < aodfarray[i]->objidlen; j++) */ /* log_printf ("%02X", aodfarray[i]->objid[j]); */ /* Note that there is no need to percent escape the label * because all white space have been replaced by '_'. */ if (is_label) put_membuf_printf (&mb, "%s%s", i? " ":"", (aodfarray[i]->label && *aodfarray[i]->label)? aodfarray[i]->label:"X"); else put_membuf_printf (&mb, "%s%d", i? " ":"", iso7816_verify_status (app_get_slot (app), aodfarray[i]->pin_reference)); } put_membuf( &mb, "", 1); p = get_membuf (&mb, NULL); if (!p) return gpg_error_from_syserror (); err = send_status_direct (ctrl, is_label? "CHV-LABEL":"CHV-STATUS", p); xfree (p); return err; } else if (!strcmp (name, "KEY-LABEL")) { /* Send KEY-LABEL lines for all private key objects. */ const char *label; char *idbuf, *labelbuf; for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) { idbuf = keyref_from_prkdf (app, prkdf); if (!idbuf) return gpg_error_from_syserror (); label = (prkdf->label && *prkdf->label)? prkdf->label : "-"; labelbuf = percent_data_escape (0, NULL, label, strlen (label)); if (!labelbuf) { xfree (idbuf); return gpg_error_from_syserror (); } send_status_info (ctrl, name, idbuf, strlen (idbuf), labelbuf, strlen(labelbuf), NULL, 0); xfree (idbuf); xfree (labelbuf); } return 0; } else if (!strcmp (name, "KEY-FPR")) { /* Send KEY-FPR for the two openpgp keys. */ for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) { if (app->app_local->any_gpgusage) { if (prkdf->gpgusage.sign) break; } else { if (prkdf->usageflags.sign || prkdf->usageflags.sign_recover) break; } } if (prkdf) send_key_fpr (app, ctrl, prkdf, 1); for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) { if (app->app_local->any_gpgusage) { if (prkdf->gpgusage.encr) break; } else { if (prkdf->usageflags.decrypt || prkdf->usageflags.unwrap) break; } } if (prkdf) send_key_fpr (app, ctrl, prkdf, 2); return 0; } return gpg_error (GPG_ERR_INV_NAME); } /* Micardo cards require special treatment. This is a helper for the crypto functions to manage the security environment. We expect that the key file has already been selected. FID is the one of the selected key. */ static gpg_error_t micardo_mse (app_t app, unsigned short fid) { gpg_error_t err; int recno; unsigned short refdata = 0; int se_num; unsigned char msebuf[10]; /* Read the KeyD file containing extra information on keys. */ err = iso7816_select_file (app_get_slot (app), 0x0013, 0); if (err) { log_error ("p15: error reading EF_keyD: %s\n", gpg_strerror (err)); return err; } for (recno = 1, se_num = -1; ; recno++) { unsigned char *buffer; size_t buflen; size_t n, nn; const unsigned char *p, *pp; err = iso7816_read_record (app_get_slot (app), recno, 1, 0, &buffer, &buflen); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) break; /* ready */ if (err) { log_error ("p15: error reading EF_keyD record: %s\n", gpg_strerror (err)); return err; } if (opt.verbose) { log_info (buffer, buflen, "p15: keyD record: "); log_printhex (buffer, buflen, ""); } p = find_tlv (buffer, buflen, 0x83, &n); if (p && n == 4 && ((p[2]<<8)|p[3]) == fid) { refdata = ((p[0]<<8)|p[1]); /* Locate the SE DO and the there included sec env number. */ p = find_tlv (buffer, buflen, 0x7b, &n); if (p && n) { pp = find_tlv (p, n, 0x80, &nn); if (pp && nn == 1) { se_num = *pp; xfree (buffer); break; /* found. */ } } } xfree (buffer); } if (se_num == -1) { log_error ("p15: CRT for keyfile %04hX not found\n", fid); return gpg_error (GPG_ERR_NOT_FOUND); } /* Restore the security environment to SE_NUM if needed */ if (se_num) { err = iso7816_manage_security_env (app_get_slot (app), 0xf3, se_num, NULL, 0); if (err) { log_error ("p15: restoring SE to %d failed: %s\n", se_num, gpg_strerror (err)); return err; } } /* Set the DST reference data. */ msebuf[0] = 0x83; msebuf[1] = 0x03; msebuf[2] = 0x80; msebuf[3] = (refdata >> 8); msebuf[4] = refdata; err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xb6, msebuf, 5); if (err) { log_error ("p15: setting SE to reference file %04hX failed: %s\n", refdata, gpg_strerror (err)); return err; } return 0; } /* Prepare the verification of the PIN for the key PRKDF by checking * the AODF and selecting the key file. KEYREF is used for error * messages. AODF may be NULL if no verification needs to be done. */ static gpg_error_t prepare_verify_pin (app_t app, const char *keyref, prkdf_object_t prkdf, aodf_object_t aodf) { gpg_error_t err; int i; if (aodf) { if (opt.verbose) { log_info ("p15: using AODF %04hX id=", aodf->fid); for (i=0; i < aodf->objidlen; i++) log_printf ("%02X", aodf->objid[i]); log_printf ("\n"); } if (aodf->authid && opt.verbose) log_info ("p15: PIN is controlled by another authentication token\n"); if (aodf->pinflags.integrity_protected || aodf->pinflags.confidentiality_protected) { log_error ("p15: PIN verification requires" " unsupported protection method\n"); return gpg_error (GPG_ERR_BAD_PIN_METHOD); } if (!aodf->stored_length && aodf->pinflags.needs_padding) { log_error ("p15: PIN verification requires" " padding but no length known\n"); return gpg_error (GPG_ERR_INV_CARD); } } if (app->app_local->card_product == CARD_PRODUCT_DTRUST) { /* According to our protocol analysis we need to select a * special AID here. Before that the master file needs to be * selected. (RID A000000167 is assigned to IBM) */ static char const dtrust_aid[] = { 0xA0, 0x00, 0x00, 0x01, 0x67, 0x45, 0x53, 0x49, 0x47, 0x4E }; err = iso7816_select_mf (app_get_slot (app)); if (!err) err = iso7816_select_application (app_get_slot (app), dtrust_aid, sizeof dtrust_aid, 0); if (err) log_error ("p15: error selecting D-TRUST's AID for key %s: %s\n", keyref, gpg_strerror (err)); } else if (prkdf) { /* Standard case: Select the key file. Note that this may * change the security environment thus we need to do it before * PIN verification. */ err = select_ef_by_path (app, prkdf->path, prkdf->pathlen); if (err) log_error ("p15: error selecting file for key %s: %s\n", keyref, gpg_strerror (err)); } else { log_info ("p15: skipping EF selection for auth object '%s'\n", keyref); err = 0; } return err; } static int any_control_or_space (const char *string) { const unsigned char *s; for (s = string; *s; s++) if (*s <= 0x20 || *s >= 0x7f) return 1; return 0; } static int any_control_or_space_mem (const void *buffer, size_t buflen) { const unsigned char *s; for (s = buffer; buflen; s++, buflen--) if (*s <= 0x20 || *s >= 0x7f) return 1; return 0; } /* Return a malloced serial number to be shown to the user. PRKDF is * used to get it from a certificate; PRKDF may be NULL. */ static char * get_dispserialno (app_t app, prkdf_object_t prkdf) { char *serial; const unsigned char *s; int i; size_t n; /* We prefer the SerialNumber RDN from the Subject-DN but we don't * use it if it features a percent sign (special character in pin * prompts) or has any control character. For some cards we use a * different strategy. */ if (app->app_local->card_product == CARD_PRODUCT_RSCS) { /* We use only the right 8 hex digits. */ serial = app_get_serialno (app); if (serial && (n=strlen (serial)) > 8) memmove (serial, serial + n - 8, 9); } else if (IS_CARDOS_5 (app) && app->app_local->manufacturer_id && !ascii_strcasecmp (app->app_local->manufacturer_id, "Technology Nexus") && APP_CARD(app)->serialno && APP_CARD(app)->serialnolen == 4+9 && !memcmp (APP_CARD(app)->serialno, "\xff\x00\x00\xff", 4) && !any_control_or_space_mem (APP_CARD(app)->serialno + 4, 9)) { /* Sample: ff0000ff354830313232363537 -> "5H01 2265 7" */ serial = xtrymalloc (9+2+1); if (serial) { s = APP_CARD(app)->serialno + 4; for (i=0; i < 4; i++) serial[i] = *s++; serial[i++] = ' '; for (; i < 9; i++) serial[i] = *s++; serial[i++] = ' '; serial[i++] = *s; serial[i] = 0; } } else if (prkdf && prkdf->serial_number && *prkdf->serial_number && !strchr (prkdf->serial_number, '%') && !any_control_or_space (prkdf->serial_number)) { serial = xtrystrdup (prkdf->serial_number); } else { serial = app_get_serialno (app); } return serial; } /* Return an allocated string to be used as prompt. PRKDF may be * NULL. Returns NULL on malloc error. */ static char * make_pin_prompt (app_t app, int remaining, const char *firstline, prkdf_object_t prkdf) { char *serial, *tmpbuf, *result; const char *holder = NULL; serial = get_dispserialno (app, prkdf); if (app->app_local->card_product == CARD_PRODUCT_GENUA) { /* The label of the first non SO-PIN is used for the holder. */ aodf_object_t aodf; for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) if (aodf->auth_type == AUTH_TYPE_PIN && !aodf->pinflags.so_pin && aodf->label) { holder = aodf->label; break; } } if (holder) ; else if (prkdf && prkdf->common_name) holder = prkdf->common_name; else if (app->app_local->token_label) holder = app->app_local->token_label; else holder = ""; /* TRANSLATORS: Put a \x1f right before a colon. This can be * used by pinentry to nicely align the names and values. Keep * the %s at the start and end of the string. */ result = xtryasprintf (_("%s" "Number\x1f: %s%%0A" "Holder\x1f: %s" "%s"), "\x1e", serial, holder, ""); xfree (serial); if (!result) return NULL; /* Out of core. */ /* Append a "remaining attempts" info if needed. */ if (remaining != -1 && remaining < 3) { char *rembuf; /* TRANSLATORS: This is the number of remaining attempts to * enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */ rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining); if (rembuf) { tmpbuf = strconcat (firstline, "%0A%0A", result, "%0A%0A", rembuf, NULL); xfree (rembuf); } else tmpbuf = NULL; xfree (result); result = tmpbuf; } else { tmpbuf = strconcat (firstline, "%0A%0A", result, NULL); xfree (result); result = tmpbuf; } return result; } /* Given the private key object PRKDF and its authentication object * AODF ask for the PIN and verify that PIN. If AODF is NULL, no * authentication is done. */ static gpg_error_t verify_pin (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, prkdf_object_t prkdf, aodf_object_t aodf) { gpg_error_t err; char *pinvalue; size_t pinvaluelen; const char *label; const char *errstr; const char *s; int remaining; int pin_reference; int verified = 0; int i; if (!aodf) return 0; pin_reference = aodf->pin_reference_valid? aodf->pin_reference : 0; if (IS_CARDOS_5 (app)) { /* We know that this card supports a verify status check. Note * that in contrast to PIV cards ISO7816_VERIFY_NOT_NEEDED is * not supported. We also don't use the pin_verified cache * status because that is not as reliable as to ask the card * about its state. */ if (prkdf) /* Clear the cache which we don't use. */ prkdf->pin_verified = 0; remaining = iso7816_verify_status (app_get_slot (app), pin_reference); if (remaining == ISO7816_VERIFY_NOT_NEEDED) { verified = 1; remaining = -1; } else if (remaining < 0) remaining = -1; /* We don't care about the concrete error. */ else if (remaining < 3) log_info ("p15: PIN has %d attempts left\n", remaining); } else remaining = -1; /* Unknown. */ /* Check whether we already verified it. */ if (prkdf && (prkdf->pin_verified || verified)) return 0; /* Already done. */ if (prkdf && prkdf->usageflags.non_repudiation && (app->app_local->card_type == CARD_TYPE_BELPIC || app->app_local->card_product == CARD_PRODUCT_DTRUST)) label = _("||Please enter the PIN for the key to create " "qualified signatures."); else if (aodf->pinflags.so_pin) label = _("|A|Please enter the Admin PIN"); else if (aodf->pinflags.unblocking_pin) label = _("|P|Please enter the PIN Unblocking Code (PUK) " "for the standard keys."); else label = _("||Please enter the PIN for the standard keys."); { char *prompt = make_pin_prompt (app, remaining, label, prkdf); if (!prompt) err = gpg_error_from_syserror (); else err = pincb (pincb_arg, prompt, &pinvalue); xfree (prompt); } if (err) { log_info ("p15: PIN callback returned error: %s\n", gpg_strerror (err)); return err; } /* We might need to cope with UTF8 things here. Not sure how min_length etc. are exactly defined, for now we take them as a plain octet count. */ if (strlen (pinvalue) < aodf->min_length) { log_error ("p15: PIN is too short; minimum length is %lu\n", aodf->min_length); err = gpg_error (GPG_ERR_BAD_PIN); } else if (aodf->stored_length && strlen (pinvalue) > aodf->stored_length) { /* This would otherwise truncate the PIN silently. */ log_error ("p15: PIN is too large; maximum length is %lu\n", aodf->stored_length); err = gpg_error (GPG_ERR_BAD_PIN); } else if (aodf->max_length_valid && strlen (pinvalue) > aodf->max_length) { log_error ("p15: PIN is too large; maximum length is %lu\n", aodf->max_length); err = gpg_error (GPG_ERR_BAD_PIN); } if (err) { xfree (pinvalue); return err; } errstr = NULL; err = 0; switch (aodf->pintype) { case PIN_TYPE_BCD: for (s=pinvalue; digitp (s); s++) ; if (*s) { errstr = "Non-numeric digits found in PIN"; err = gpg_error (GPG_ERR_BAD_PIN); } break; case PIN_TYPE_ASCII_NUMERIC: for (s=pinvalue; *s && !(*s & 0x80); s++) ; if (*s) { errstr = "Non-ascii characters found in PIN"; err = gpg_error (GPG_ERR_BAD_PIN); } break; case PIN_TYPE_UTF8: break; case PIN_TYPE_HALF_NIBBLE_BCD: errstr = "PIN type Half-Nibble-BCD is not supported"; break; case PIN_TYPE_ISO9564_1: errstr = "PIN type ISO9564-1 is not supported"; break; default: errstr = "Unknown PIN type"; break; } if (errstr) { log_error ("p15: can't verify PIN: %s\n", errstr); xfree (pinvalue); return err? err : gpg_error (GPG_ERR_BAD_PIN_METHOD); } if (aodf->pintype == PIN_TYPE_BCD ) { char *paddedpin; int ndigits; for (ndigits=0, s=pinvalue; *s; ndigits++, s++) ; paddedpin = xtrymalloc (aodf->stored_length+1); if (!paddedpin) { err = gpg_error_from_syserror (); xfree (pinvalue); return err; } i = 0; paddedpin[i++] = 0x20 | (ndigits & 0x0f); for (s=pinvalue; i < aodf->stored_length && *s && s[1]; s = s+2 ) paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f)); if (i < aodf->stored_length && *s) paddedpin[i++] = (((*s - '0') << 4) |((aodf->pad_char_valid?aodf->pad_char:0)&0x0f)); if (aodf->pinflags.needs_padding) { while (i < aodf->stored_length) paddedpin[i++] = aodf->pad_char_valid? aodf->pad_char : 0; } xfree (pinvalue); pinvalue = paddedpin; pinvaluelen = i; } else if (aodf->pinflags.needs_padding) { char *paddedpin; paddedpin = xtrymalloc (aodf->stored_length+1); if (!paddedpin) { err = gpg_error_from_syserror (); xfree (pinvalue); return err; } for (i=0, s=pinvalue; i < aodf->stored_length && *s; i++, s++) paddedpin[i] = *s; /* Not sure what padding char to use if none has been set. For now we use 0x00; maybe a space would be better. */ for (; i < aodf->stored_length; i++) paddedpin[i] = aodf->pad_char_valid? aodf->pad_char : 0; paddedpin[i] = 0; pinvaluelen = i; xfree (pinvalue); pinvalue = paddedpin; } else pinvaluelen = strlen (pinvalue); /* log_printhex (pinvalue, pinvaluelen, */ /* "about to verify with ref %lu pin:", pin_reference); */ err = iso7816_verify (app_get_slot (app), pin_reference, pinvalue, pinvaluelen); xfree (pinvalue); if (err) { log_error ("p15: PIN verification failed: %s\n", gpg_strerror (err)); return err; } if (opt.verbose) log_info ("p15: PIN verification succeeded\n"); if (prkdf) prkdf->pin_verified = 1; return 0; } /* Handler for the PKSIGN command. Create the signature and return the allocated result in OUTDATA. If a PIN is required, the PINCB will be used to ask for the PIN; that callback should return the PIN in an allocated buffer and store that as the 3rd argument. */ static gpg_error_t do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; prkdf_object_t prkdf; /* The private key object. */ aodf_object_t aodf; /* The associated authentication object. */ int mse_done = 0; /* Set to true if the MSE has been done. */ unsigned int digestlen; /* Length of the hash. */ int exmode, le_value; unsigned char oidbuf[64]; size_t oidbuflen; size_t n; unsigned char *indata_buffer = NULL; /* Malloced helper. */ (void)ctrl; if (!keyidstr || !*keyidstr || !indatalen) return gpg_error (GPG_ERR_INV_VALUE); err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); if (err) return err; if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover || prkdf->usageflags.non_repudiation || prkdf->gpgusage.cert || prkdf->gpgusage.sign || prkdf->gpgusage.auth )) { log_error ("p15: key %s may not be used for signing\n", keyidstr); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if (!prkdf->authid) { log_error ("p15: no authentication object defined for %s\n", keyidstr); /* fixme: we might want to go ahead and do without PIN verification. */ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); } /* Find the authentication object to this private key object. */ for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) if (aodf->objidlen == prkdf->authidlen && !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen)) break; if (!aodf) log_info ("p15: no authentication for %s needed\n", keyidstr); /* We need some more info about the key - get the keygrip to * populate these fields. */ err = keygrip_from_prkdf (app, prkdf); if (err) { log_error ("p15: keygrip_from_prkdf failed: %s\n", gpg_strerror (err)); return err; } digestlen = gcry_md_get_algo_dlen (hashalgo); /* We handle ECC separately from RSA so that we do not need to touch * working code. In particular we prepare the input data before the * verify and a possible MSE. */ if (prkdf->is_ecc) { if (digestlen != 32 && digestlen != 48 && digestlen != 64) { log_error ("p15: ECC signing not possible: dlen=%u\n", digestlen); err = gpg_error (GPG_ERR_DIGEST_ALGO); goto leave; } if (indatalen == digestlen) ; /* Already prepared. */ else if (indatalen > digestlen) { /* Assume a PKCS#1 prefix and remove it. */ oidbuflen = sizeof oidbuf; err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen); if (err) { log_error ("p15: no OID for hash algo %d\n", hashalgo); err = gpg_error (GPG_ERR_INTERNAL); goto leave; } if (indatalen != oidbuflen + digestlen || memcmp (indata, oidbuf, oidbuflen)) { log_error ("p15: input data too long for ECC: len=%zu\n", indatalen); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } indata = (const char*)indata + oidbuflen; indatalen -= oidbuflen; } else { log_error ("p15: input data too short for ECC: len=%zu\n", indatalen); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } } else /* Prepare RSA input. */ { unsigned int framelen; unsigned char *frame; int i; framelen = (prkdf->keynbits+7) / 8; if (!framelen) { log_error ("p15: key length unknown" " - can't prepare PKCS#v1.5 frame\n"); err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } oidbuflen = sizeof oidbuf; if (!hashalgo) { /* We assume that indata already has the required * digestinfo; thus merely prepend the padding below. */ } else if ((err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen))) { log_debug ("p15: no OID for hash algo %d\n", hashalgo); goto leave; } else { if (indatalen == digestlen) { /* Plain hash in INDATA; prepend the digestinfo. */ indata_buffer = xtrymalloc (oidbuflen + indatalen); if (!indata_buffer) { err = gpg_error_from_syserror (); goto leave; } memcpy (indata_buffer, oidbuf, oidbuflen); memcpy (indata_buffer+oidbuflen, indata, indatalen); indata = indata_buffer; indatalen = oidbuflen + indatalen; } else if (indatalen == oidbuflen + digestlen && !memcmp (indata, oidbuf, oidbuflen)) ; /* We already got the correct prefix. */ else { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("p15: bad input for signing with RSA and hash %d\n", hashalgo); goto leave; } } /* Now prepend the pkcs#v1.5 padding. We require at least 8 * byte of padding and 3 extra bytes for the prefix and the * delimiting nul. */ if (!indatalen || indatalen + 8 + 4 > framelen) { err = gpg_error (GPG_ERR_INV_VALUE); log_error ("p15: input does not fit into a %u bit PKCS#v1.5 frame\n", 8*framelen); goto leave; } frame = xtrymalloc (framelen); if (!frame) { err = gpg_error_from_syserror (); goto leave; } if (app->app_local->card_type == CARD_TYPE_BELPIC) { /* This card wants only the plain hash w/o any prefix. */ /* FIXME: We may want to remove this code because it is unlikely * that such cards are still in use. */ memcpy (frame, indata, indatalen); framelen = indatalen; } else { n = 0; frame[n++] = 0; frame[n++] = 1; /* Block type. */ i = framelen - indatalen - 3 ; memset (frame+n, 0xff, i); n += i; frame[n++] = 0; /* Delimiter. */ memcpy (frame+n, indata, indatalen); n += indatalen; log_assert (n == framelen); } /* And now put it into the indata_buffer. */ xfree (indata_buffer); indata_buffer = frame; indata = indata_buffer; indatalen = framelen; } /* Prepare PIN verification. This is split so that we can do * MSE operation for some task after having selected the key file but * before sending the verify APDU. */ err = prepare_verify_pin (app, keyidstr, prkdf, aodf); if (err) return err; /* Due to the fact that the non-repudiation signature on a BELPIC card requires a verify immediately before the DSO we set the MSE before we do the verification. Other cards might also allow this but I don't want to break anything, thus we do it only for the BELPIC card here. FIXME: see comment above about these cards. */ if (app->app_local->card_type == CARD_TYPE_BELPIC) { unsigned char mse[5]; mse[0] = 4; /* Length of the template. */ mse[1] = 0x80; /* Algorithm reference tag. */ if (hashalgo == MD_USER_TLS_MD5SHA1) mse[2] = 0x01; /* Let card do pkcs#1 0xFF padding. */ else mse[2] = 0x02; /* RSASSA-PKCS1-v1.5 using SHA1. */ mse[3] = 0x84; /* Private key reference tag. */ mse[4] = prkdf->key_reference_valid? prkdf->key_reference : 0x82; err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB6, mse, sizeof mse); mse_done = 1; } if (err) { log_error ("p15: MSE failed: %s\n", gpg_strerror (err)); goto leave; } /* Now that we have all the information available run the actual PIN * verification.*/ err = verify_pin (app, pincb, pincb_arg, prkdf, aodf); if (err) return err; /* Manage security environment needs to be tweaked for certain cards. */ if (mse_done) err = 0; else if (app->app_local->card_type == CARD_TYPE_TCOS) { /* TCOS creates signatures always using the local key 0. MSE may not be used. */ } else if (app->app_local->card_type == CARD_TYPE_MICARDO) { if (!prkdf->pathlen) err = gpg_error (GPG_ERR_BUG); else err = micardo_mse (app, prkdf->path[prkdf->pathlen-1]); } else if (prkdf->key_reference_valid) { unsigned char mse[3]; mse[0] = 0x84; /* Select asym. key. */ mse[1] = 1; mse[2] = prkdf->key_reference; err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB6, mse, sizeof mse); } if (err) { log_error ("p15: MSE failed: %s\n", gpg_strerror (err)); goto leave; } if (prkdf->keyalgo == GCRY_PK_RSA && prkdf->keynbits >= 2048) { exmode = 1; le_value = prkdf->keynbits / 8; } else { exmode = 0; le_value = 0; } err = iso7816_compute_ds (app_get_slot (app), exmode, indata, indatalen, le_value, outdata, outdatalen); leave: xfree (indata_buffer); return err; } /* Handler for the PKAUTH command. This is basically the same as the PKSIGN command but we first check that the requested key is suitable for authentication; that is, it must match the criteria used for the attribute $AUTHKEYID. See do_sign for calling conventions; there is no HASHALGO, though. */ static gpg_error_t do_auth (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; prkdf_object_t prkdf; int algo; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); if (err) return err; if (!(prkdf->usageflags.sign || prkdf->gpgusage.auth)) { log_error ("p15: key %s may not be used for authentication\n", keyidstr); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1; return do_sign (app, ctrl, keyidstr, algo, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); } /* Handler for the PKDECRYPT command. Decrypt the data in INDATA and * return the allocated result in OUTDATA. If a PIN is required the * PINCB will be used to ask for the PIN; it should return the PIN in * an allocated buffer and put it into PIN. */ static gpg_error_t do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen, unsigned int *r_info) { gpg_error_t err; prkdf_object_t prkdf; /* The private key object. */ aodf_object_t aodf; /* The associated authentication object. */ int exmode, le_value, padind; (void)ctrl; (void)r_info; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); if (!indatalen || !indata || !outdatalen || !outdata) return gpg_error (GPG_ERR_INV_ARG); err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); if (err) return err; if (!(prkdf->usageflags.decrypt || prkdf->usageflags.unwrap || prkdf->gpgusage.encr )) { log_error ("p15: key %s may not be used for decryption\n", keyidstr); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } /* Find the authentication object to this private key object. */ if (!prkdf->authid) { log_error ("p15: no authentication object defined for %s\n", keyidstr); /* fixme: we might want to go ahead and do without PIN verification. */ return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); } for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) if (aodf->objidlen == prkdf->authidlen && !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen)) break; if (!aodf) log_info ("p15: no authentication for %s needed\n", keyidstr); /* We need some more info about the key - get the keygrip to * populate these fields. */ err = keygrip_from_prkdf (app, prkdf); if (err) { log_error ("p15: keygrip_from_prkdf failed: %s\n", gpg_strerror (err)); return err; } /* Verify the PIN. */ err = prepare_verify_pin (app, keyidstr, prkdf, aodf); if (!err) err = verify_pin (app, pincb, pincb_arg, prkdf, aodf); if (err) return err; if (prkdf->is_ecc && IS_CARDOS_5(app)) { err = iso7816_manage_security_env (app_get_slot (app), 0xF3, 0x01, NULL, 0); if (err) { log_error ("p15: MSE failed: %s\n", gpg_strerror (err)); return err; } } /* The next is guess work for CardOS. */ if (app->app_local->card_product == CARD_PRODUCT_DTRUST) { /* From analyzing an USB trace of a Windows signing application * we see that the SE is simply reset to 0x14. It seems to be * sufficient to do this for decryption; signing still works * with the standard code despite that our trace showed that * there the SE is restored to 0x09. Note that the special * D-Trust AID is in any case select by prepare_verify_pin. * * Hey, D-Trust please hand over the specs so that you can * actually sell your cards and we can properly implement it; * other vendors understand this and do not demand ridiculous * paper work or complicated procedures to get samples. */ err = iso7816_manage_security_env (app_get_slot (app), 0xF3, 0x14, NULL, 0); } else if (prkdf->key_reference_valid) { unsigned char mse[9]; int i; /* Note: This works with CardOS but the D-Trust card has the * problem that the next created signature would be broken. */ i = 0; if (!prkdf->is_ecc) { mse[i++] = 0x80; /* Algorithm reference. */ mse[i++] = 1; mse[i++] = 0x0a; /* RSA, no padding. */ } mse[i++] = 0x84; /* Key reference. */ mse[i++] = 1; mse[i++] = prkdf->key_reference; if (prkdf->is_ecc && IS_CARDOS_5(app)) { mse[i++] = 0x95; /* ???. */ mse[i++] = 1; mse[i++] = 0x40; } log_assert (i <= DIM(mse)); err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB8, mse, i); } /* Check for MSE error. */ if (err) { log_error ("p15: MSE failed: %s\n", gpg_strerror (err)); return err; } exmode = le_value = 0; padind = 0; if (prkdf->keyalgo == GCRY_PK_RSA && prkdf->keynbits >= 2048) { exmode = 1; /* Extended length w/o a limit. */ le_value = prkdf->keynbits / 8; } if (app->app_local->card_product == CARD_PRODUCT_DTRUST) padind = 0x81; if (prkdf->is_ecc && IS_CARDOS_5(app)) { if ((indatalen & 1) && *(const char *)indata == 0x04) { /* Strip indicator byte. */ indatalen--; indata = (const char *)indata + 1; } err = iso7816_pso_csv (app_get_slot (app), exmode, indata, indatalen, le_value, outdata, outdatalen); } else { err = iso7816_decipher (app_get_slot (app), exmode, indata, indatalen, le_value, padind, outdata, outdatalen); } return err; } /* Perform a simple verify operation for the PIN specified by * KEYIDSTR. Note that we require a key reference which is then used * to select the authentication object. Return GPG_ERR_NO_PIN if a * PIN is not required for using the private key KEYIDSTR. */ static gpg_error_t do_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; prkdf_object_t prkdf; /* The private key object. */ aodf_object_t aodf; /* The associated authentication object. */ (void)ctrl; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); if (err && gpg_err_code (err) != GPG_ERR_INV_ID && gpg_err_code (err) != GPG_ERR_NOT_FOUND) return err; if (err) /* Not found or invalid - assume it is the label. */ { prkdf = NULL; for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) if (aodf->label && !ascii_strcasecmp (aodf->label, keyidstr)) break; if (!aodf) return err; /* Re-use the original error code. */ } else /* Find the authentication object to this private key object. */ { if (!prkdf->authid) { log_error ("p15: no authentication object defined for %s\n", keyidstr); return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); } for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next) if (aodf->objidlen == prkdf->authidlen && !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen)) break; if (!aodf) /* None found. */ return gpg_error (GPG_ERR_NO_PIN); } err = prepare_verify_pin (app, keyidstr, prkdf, aodf); if (!err) err = verify_pin (app, pincb, pincb_arg, prkdf, aodf); return err; } /* Process the various keygrip based info requests. */ static gpg_error_t do_with_keygrip (app_t app, ctrl_t ctrl, int action, const char *want_keygripstr, int capability) { gpg_error_t err; char *serialno = NULL; int as_data = 0; prkdf_object_t prkdf; /* First a quick check for valid parameters. */ switch (action) { case KEYGRIP_ACTION_LOOKUP: if (!want_keygripstr) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } break; case KEYGRIP_ACTION_SEND_DATA: as_data = 1; break; case KEYGRIP_ACTION_WRITE_STATUS: break; default: err = gpg_error (GPG_ERR_INV_ARG); goto leave; } /* Allocate the s/n string if needed. */ if (action != KEYGRIP_ACTION_LOOKUP) { serialno = app_get_serialno (app); if (!serialno) { err = gpg_error_from_syserror (); goto leave; } } for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) { if (keygrip_from_prkdf (app, prkdf)) continue; if (action == KEYGRIP_ACTION_LOOKUP) { if (!strcmp (prkdf->keygrip, want_keygripstr)) { err = 0; /* Found */ goto leave; } } else if (!want_keygripstr || !strcmp (prkdf->keygrip, want_keygripstr)) { char *keyref; char usage[5]; if (capability == GCRY_PK_USAGE_SIGN) { if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover || prkdf->usageflags.non_repudiation)) continue; } else if (capability == GCRY_PK_USAGE_ENCR) { if (!(prkdf->usageflags.decrypt || prkdf->usageflags.unwrap)) continue; } else if (capability == GCRY_PK_USAGE_AUTH) { if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover)) continue; } keyref = keyref_from_prkdf (app, prkdf); if (!keyref) { err = gpg_error_from_syserror (); goto leave; } set_usage_string (usage, prkdf); send_keyinfo (ctrl, as_data, prkdf->keygrip, serialno, keyref, usage); xfree (keyref); if (want_keygripstr) { err = 0; /* Found */ goto leave; } } } /* Return an error so that the dispatcher keeps on looping over the * other applications. For clarity we use a different error code * when listing all keys. Note that in lookup mode WANT_KEYGRIPSTR * is not NULL. */ if (!want_keygripstr) err = gpg_error (GPG_ERR_TRUE); else err = gpg_error (GPG_ERR_NOT_FOUND); leave: xfree (serialno); return err; } /* Assume that EF(DIR) has been selected. Read its content and figure out the home EF of pkcs#15. Return that home DF or 0 if not found and the value at the address of BELPIC indicates whether it was found by the belpic aid. */ static unsigned short read_home_df (int slot, int *r_belpic) { gpg_error_t err; unsigned char *buffer; const unsigned char *p, *pp; size_t buflen, n, nn; unsigned short result = 0; *r_belpic = 0; err = iso7816_read_binary (slot, 0, 0, &buffer, &buflen); if (err) { log_error ("p15: error reading EF(DIR): %s\n", gpg_strerror (err)); return 0; } /* FIXME: We need to scan all records. */ p = find_tlv (buffer, buflen, 0x61, &n); if (p && n) { pp = find_tlv (p, n, 0x4f, &nn); if (pp && ((nn == sizeof pkcs15_aid && !memcmp (pp, pkcs15_aid, nn)) || (*r_belpic = (nn == sizeof pkcs15be_aid && !memcmp (pp, pkcs15be_aid, nn))))) { pp = find_tlv (p, n, 0x50, &nn); if (pp && opt.verbose) log_info ("p15: application label from EF(DIR) is '%.*s'\n", (int)nn, pp); pp = find_tlv (p, n, 0x51, &nn); if (pp && nn == 4 && *pp == 0x3f && !pp[1]) { result = ((pp[2] << 8) | pp[3]); if (opt.verbose) log_info ("p15: application directory is 0x%04hX\n", result); } } } xfree (buffer); return result; } /* Select the PKCS#15 application on the card in SLOT. */ gpg_error_t app_select_p15 (app_t app) { int slot = app_get_slot (app); int rc; unsigned short def_home_df = 0; card_type_t card_type = CARD_TYPE_UNKNOWN; int direct = 0; int is_belpic = 0; unsigned char *fci = NULL; size_t fcilen; rc = iso7816_select_application_ext (slot, pkcs15_aid, sizeof pkcs15_aid, 1, &fci, &fcilen); if (rc) { /* Not found: Try to locate it from 2F00. We use direct path selection here because it seems that the Belgian eID card does only allow for that. Many other cards supports this selection method too. Note, that we don't use select_application above for the Belgian card - the call works but it seems that it does not switch to the correct DF. Using the 2f02 just works. */ unsigned short path[1] = { 0x2f00 }; rc = iso7816_select_path (slot, path, 1, 0); if (!rc) { direct = 1; def_home_df = read_home_df (slot, &is_belpic); if (def_home_df) { path[0] = def_home_df; rc = iso7816_select_path (slot, path, 1, 0); } } } if (rc) { /* Still not found: Try the default DF. */ def_home_df = DEFAULT_HOME_DF; rc = iso7816_select_file (slot, def_home_df, 1); } if (!rc) { /* Determine the type of the card. The general case is to look it up from the ATR table. For the Belgian eID card we know it instantly from the AID. */ if (is_belpic) { card_type = CARD_TYPE_BELPIC; } else { unsigned char *atr; size_t atrlen; int i; atr = apdu_get_atr (app_get_slot (app), &atrlen); if (!atr) rc = gpg_error (GPG_ERR_INV_CARD); else { for (i=0; card_atr_list[i].atrlen; i++) if (card_atr_list[i].atrlen == atrlen && !memcmp (card_atr_list[i].atr, atr, atrlen)) { card_type = card_atr_list[i].type; break; } xfree (atr); } } } if (!rc) { app->apptype = APPTYPE_P15; app->app_local = xtrycalloc (1, sizeof *app->app_local); if (!app->app_local) { rc = gpg_error_from_syserror (); goto leave; } /* Set the home DF from the FCI returned by the select. */ if (!def_home_df && fci) { const unsigned char *s; size_t n; s = find_tlv (fci, fcilen, 0x83, &n); if (s && n == 2) def_home_df = buf16_to_ushort (s); else { if (fcilen) log_printhex (fci, fcilen, "fci:"); log_info ("p15: select did not return the DF - using default\n"); def_home_df = DEFAULT_HOME_DF; } } app->app_local->home_df = def_home_df; /* Store the card type. FIXME: We might want to put this into the common APP structure. */ app->app_local->card_type = card_type; app->app_local->card_product = CARD_PRODUCT_UNKNOWN; /* Store whether we may and should use direct path selection. */ switch (card_type) { case CARD_TYPE_CARDOS_50: case CARD_TYPE_CARDOS_53: direct = 1; break; case CARD_TYPE_AET: app->app_local->no_extended_mode = 1; break; default: /* Use whatever has been determined above. */ break; } app->app_local->direct_path_selection = direct; /* Read basic information and thus check whether this is a real card. */ rc = read_p15_info (app); if (rc) goto leave; /* Special serial number munging. We need to check for a German prototype card right here because we need to access to EF(TokenInfo). We mark such a serial number by the using a prefix of FF0100. */ if (APP_CARD(app)->serialnolen == 12 && !memcmp (APP_CARD(app)->serialno, "\xD2\x76\0\0\0\0\0\0\0\0\0\0", 12)) { /* This is a German card with a silly serial number. Try to get the serial number from the EF(TokenInfo). . */ unsigned char *p; /* FIXME: actually get it from EF(TokenInfo). */ p = xtrymalloc (3 + APP_CARD(app)->serialnolen); if (!p) rc = gpg_error (gpg_err_code_from_errno (errno)); else { memcpy (p, "\xff\x01", 3); memcpy (p+3, APP_CARD(app)->serialno, APP_CARD(app)->serialnolen); APP_CARD(app)->serialnolen += 3; xfree (APP_CARD(app)->serialno); APP_CARD(app)->serialno = p; } } app->fnc.deinit = do_deinit; app->fnc.prep_reselect = NULL; app->fnc.reselect = NULL; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.getattr = do_getattr; app->fnc.setattr = NULL; app->fnc.genkey = NULL; app->fnc.sign = do_sign; app->fnc.auth = do_auth; app->fnc.decipher = do_decipher; app->fnc.change_pin = NULL; app->fnc.check_pin = do_check_pin; app->fnc.with_keygrip = do_with_keygrip; leave: if (rc) do_deinit (app); } xfree (fci); return rc; } diff --git a/scd/app-sc-hsm.c b/scd/app-sc-hsm.c index 1ca709f72..c57ba345e 100644 --- a/scd/app-sc-hsm.c +++ b/scd/app-sc-hsm.c @@ -1,2094 +1,2095 @@ /* app-sc-hsm.c - The SmartCard-HSM card application (www.smartcard-hsm.com). * Copyright (C) 2005 Free Software Foundation, Inc. * Copyright (C) 2014 Andreas Schwier * * 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 . */ /* Code in this driver is based on app-p15.c with modifications. */ #include #include #include #include #include #include #include "scdaemon.h" #include "iso7816.h" #include "../common/tlv.h" #include "apdu.h" /* The AID of the SmartCard-HSM applet. */ static char const sc_hsm_aid[] = { 0xE8, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xC3, 0x1F, 0x02, 0x01 }; /* Special file identifier for SmartCard-HSM */ typedef enum { SC_HSM_PRKD_PREFIX = 0xC4, SC_HSM_CD_PREFIX = 0xC8, SC_HSM_DCOD_PREFIX = 0xC9, SC_HSM_CA_PREFIX = 0xCA, SC_HSM_KEY_PREFIX = 0xCC, SC_HSM_EE_PREFIX = 0xCE } fid_prefix_type_t; /* The key types supported by the SmartCard-HSM */ typedef enum { KEY_TYPE_RSA, KEY_TYPE_ECC } key_type_t; /* A bit array with for the key usage flags from the commonKeyAttributes. */ struct keyusage_flags_s { unsigned int encrypt: 1; unsigned int decrypt: 1; unsigned int sign: 1; unsigned int sign_recover: 1; unsigned int wrap: 1; unsigned int unwrap: 1; unsigned int verify: 1; unsigned int verify_recover: 1; unsigned int derive: 1; unsigned int non_repudiation: 1; }; typedef struct keyusage_flags_s keyusage_flags_t; /* This is an object to store information about a Certificate Directory File (CDF) in a format suitable for further processing by us. To keep memory management, simple we use a linked list of items; i.e. one such object represents one certificate and the list the entire CDF. */ struct cdf_object_s { /* Link to next item when used in a linked list. */ struct cdf_object_s *next; /* Length and allocated buffer with the Id of this object. */ size_t objidlen; unsigned char *objid; /* To avoid reading a certificate more than once, we cache it in an allocated memory IMAGE of IMAGELEN. */ size_t imagelen; unsigned char *image; /* EF containing certificate */ unsigned short fid; }; typedef struct cdf_object_s *cdf_object_t; /* This is an object to store information about a Private Key Directory File (PrKDF) in a format suitable for further processing by us. To keep memory management, simple we use a linked list of items; i.e. one such object represents one certificate and the list the entire PrKDF. */ struct prkdf_object_s { /* Link to next item when used in a linked list. */ struct prkdf_object_s *next; /* Key type */ key_type_t keytype; /* Key size in bits or 0 if unknown */ size_t keysize; /* Length and allocated buffer with the Id of this object. */ size_t objidlen; unsigned char *objid; /* The key's usage flags. */ keyusage_flags_t usageflags; /* The keyReference */ unsigned char key_reference; }; typedef struct prkdf_object_s *prkdf_object_t; /* Context local to this application. */ struct app_local_s { /* Information on all certificates. */ cdf_object_t certificate_info; /* Information on all trusted certificates. */ cdf_object_t trusted_certificate_info; /* Information on all private keys. */ prkdf_object_t private_key_info; }; /*** Local prototypes. ***/ static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf, unsigned char **r_cert, size_t *r_certlen); /* Release the CDF object A */ static void release_cdflist (cdf_object_t a) { while (a) { cdf_object_t tmp = a->next; xfree (a->image); xfree (a->objid); xfree (a); a = tmp; } } /* Release the PrKDF object A. */ static void release_prkdflist (prkdf_object_t a) { while (a) { prkdf_object_t tmp = a->next; xfree (a->objid); xfree (a); a = tmp; } } /* Release all local resources. */ static void do_deinit (app_t app) { if (app && app->app_local) { release_cdflist (app->app_local->certificate_info); release_cdflist (app->app_local->trusted_certificate_info); release_prkdflist (app->app_local->private_key_info); xfree (app->app_local); app->app_local = NULL; } } /* Get the list of EFs from the SmartCard-HSM. * On success a dynamically buffer containing the EF list is returned. * The caller is responsible for freeing the buffer. */ static gpg_error_t list_ef (int slot, unsigned char **result, size_t *resultlen) { int sw; if (!result || !resultlen) return gpg_error (GPG_ERR_INV_VALUE); *result = NULL; *resultlen = 0; sw = apdu_send_le (slot, 1, 0x80, 0x58, 0x00, 0x00, -1, NULL, 65536, result, resultlen); if (sw != SW_SUCCESS) { /* Make sure that pending buffers are released. */ xfree (*result); *result = NULL; *resultlen = 0; } return iso7816_map_sw (sw); } /* Do a select and a read for the file with EFID. EFID_DESC is a description of the EF to be used with error messages. On success BUFFER and BUFLEN contain the entire content of the EF. The caller must free BUFFER only on success. */ static gpg_error_t select_and_read_binary (int slot, unsigned short efid, const char *efid_desc, unsigned char **buffer, size_t *buflen, int maxread) { gpg_error_t err; unsigned char cdata[4]; int sw; cdata[0] = 0x54; /* Create ISO 7861-4 odd ins READ BINARY */ cdata[1] = 0x02; cdata[2] = 0x00; cdata[3] = 0x00; sw = apdu_send_le(slot, 1, 0x00, 0xB1, efid >> 8, efid & 0xFF, 4, cdata, maxread, buffer, buflen); if (sw == SW_EOF_REACHED) sw = SW_SUCCESS; err = iso7816_map_sw (sw); if (err) { log_error ("error reading %s (0x%04X): %s\n", efid_desc, efid, gpg_strerror (err)); return err; } return 0; } /* Parse a cert Id string (or a key Id string) and return the binary object Id string in a newly allocated buffer stored at R_OBJID and R_OBJIDLEN. On Error NULL will be stored there and an error code returned. On success caller needs to free the buffer at R_OBJID. */ static gpg_error_t parse_certid (const char *certid, unsigned char **r_objid, size_t *r_objidlen) { const char *s; size_t objidlen; unsigned char *objid; int i; *r_objid = NULL; *r_objidlen = 0; if (strncmp (certid, "HSM.", 4)) return gpg_error (GPG_ERR_INV_ID); certid += 4; for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++) ; if (*s || !objidlen || (objidlen%2)) return gpg_error (GPG_ERR_INV_ID); objidlen /= 2; objid = xtrymalloc (objidlen); if (!objid) return gpg_error_from_syserror (); for (s=certid, i=0; i < objidlen; i++, s+=2) objid[i] = xtoi_2 (s); *r_objid = objid; *r_objidlen = objidlen; return 0; } /* Find a certificate object by the certificate ID CERTID and store a pointer to it at R_CDF. */ static gpg_error_t cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf) { gpg_error_t err; size_t objidlen; unsigned char *objid; cdf_object_t cdf; err = parse_certid (certid, &objid, &objidlen); if (err) return err; for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) break; if (!cdf) for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen)) break; xfree (objid); if (!cdf) return gpg_error (GPG_ERR_NOT_FOUND); *r_cdf = cdf; return 0; } /* Find a private key object by the key Id string KEYIDSTR and store a pointer to it at R_PRKDF. */ static gpg_error_t prkdf_object_from_keyidstr (app_t app, const char *keyidstr, prkdf_object_t *r_prkdf) { gpg_error_t err; size_t objidlen; unsigned char *objid; prkdf_object_t prkdf; err = parse_certid (keyidstr, &objid, &objidlen); if (err) return err; for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen)) break; xfree (objid); if (!prkdf) return gpg_error (GPG_ERR_NOT_FOUND); *r_prkdf = prkdf; return 0; } /* Parse the BIT STRING with the keyUsageFlags from the CommonKeyAttributes. */ static gpg_error_t parse_keyusage_flags (const unsigned char *der, size_t derlen, keyusage_flags_t *usageflags) { unsigned int bits, mask; int i, unused, full; memset (usageflags, 0, sizeof *usageflags); if (!derlen) return gpg_error (GPG_ERR_INV_OBJ); unused = *der++; derlen--; if ((!derlen && unused) || unused/8 > derlen) return gpg_error (GPG_ERR_ENCODING_PROBLEM); full = derlen - (unused+7)/8; unused %= 8; mask = 0; for (i=1; unused; i <<= 1, unused--) mask |= i; /* First octet */ if (derlen) { bits = *der++; derlen--; if (full) full--; else { bits &= ~mask; mask = 0; } } else bits = 0; if ((bits & 0x80)) usageflags->encrypt = 1; if ((bits & 0x40)) usageflags->decrypt = 1; if ((bits & 0x20)) usageflags->sign = 1; if ((bits & 0x10)) usageflags->sign_recover = 1; if ((bits & 0x08)) usageflags->wrap = 1; if ((bits & 0x04)) usageflags->unwrap = 1; if ((bits & 0x02)) usageflags->verify = 1; if ((bits & 0x01)) usageflags->verify_recover = 1; /* Second octet. */ if (derlen) { bits = *der++; derlen--; if (full) full--; else { bits &= ~mask; } } else bits = 0; if ((bits & 0x80)) usageflags->derive = 1; if ((bits & 0x40)) usageflags->non_repudiation = 1; return 0; } /* Read and parse a Private Key Directory File containing a single key description in PKCS#15 format. For each private key a matching certificate description is created, if the certificate EF exists and contains a X.509 certificate. Example data: 0000 30 2A 30 13 0C 11 4A 6F 65 20 44 6F 65 20 28 52 0*0...Joe Doe (R 0010 53 41 32 30 34 38 29 30 07 04 01 01 03 02 02 74 SA2048)0.......t 0020 A1 0A 30 08 30 02 04 00 02 02 08 00 ..0.0....... Decoded example: SEQUENCE SIZE( 42 ) SEQUENCE SIZE( 19 ) UTF8-STRING SIZE( 17 ) -- label 0000 4A 6F 65 20 44 6F 65 20 28 52 53 41 32 30 34 38 Joe Doe (RSA2048 0010 29 ) SEQUENCE SIZE( 7 ) OCTET-STRING SIZE( 1 ) -- id 0000 01 BIT-STRING SIZE( 2 ) -- key usage 0000 02 74 A1 [ CONTEXT 1 ] IMPLICIT SEQUENCE SIZE( 10 ) SEQUENCE SIZE( 8 ) SEQUENCE SIZE( 2 ) OCTET-STRING SIZE( 0 ) -- empty path, req object in PKCS#15 INTEGER SIZE( 2 ) -- modulus size in bits 0000 08 00 */ static gpg_error_t read_ef_prkd (app_t app, unsigned short fid, prkdf_object_t *prkdresult, cdf_object_t *cdresult) { gpg_error_t err; unsigned char *buffer = NULL; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; int i; const unsigned char *pp; size_t nn; int where; const char *errstr = NULL; prkdf_object_t prkdf = NULL; cdf_object_t cdf = NULL; unsigned long ul; const unsigned char *objid; size_t objidlen; keyusage_flags_t usageflags; const char *s; key_type_t keytype; size_t keysize; if (!fid) return gpg_error (GPG_ERR_NO_DATA); /* No private keys. */ err = select_and_read_binary (app_get_slot (app), fid, "PrKDF", &buffer, &buflen, 255); if (err) return err; p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || (tag != TAG_SEQUENCE && tag != 0x00))) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { log_error ("error parsing PrKDF record: %s\n", gpg_strerror (err)); goto leave; } keytype = tag == 0x00 ? KEY_TYPE_ECC : KEY_TYPE_RSA; pp = p; nn = objlen; p += objlen; n -= objlen; /* Parse the commonObjectAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; /* Search the optional AuthId. We need to skip the optional Label (UTF8STRING) and the optional CommonObjectFlags (BITSTRING). */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (gpg_err_code (err) == GPG_ERR_EOF) goto no_authid; if (err) goto parse_error; if (tag == TAG_UTF8_STRING) { ppp += objlen; /* Skip the Label. */ nnn -= objlen; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (gpg_err_code (err) == GPG_ERR_EOF) goto no_authid; if (err) goto parse_error; } if (tag == TAG_BIT_STRING) { ppp += objlen; /* Skip the CommonObjectFlags. */ nnn -= objlen; where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL)) err = gpg_error (GPG_ERR_INV_OBJ); if (gpg_err_code (err) == GPG_ERR_EOF) goto no_authid; if (err) goto parse_error; } if (tag == TAG_OCTET_STRING && objlen) { /* AuthId ignored */ } no_authid: ; } /* Parse the commonKeyAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; /* Get the Id. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; objid = ppp; objidlen = objlen; ppp += objlen; nnn -= objlen; /* Get the KeyUsageFlags. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; err = parse_keyusage_flags (ppp, objlen, &usageflags); if (err) goto parse_error; ppp += objlen; nnn -= objlen; /* Find the keyReference */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto leave_cki; if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN) { /* Skip the native element. */ ppp += objlen; nnn -= objlen; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto leave_cki; if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING) { /* Skip the accessFlags. */ ppp += objlen; nnn -= objlen; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (gpg_err_code (err) == GPG_ERR_EOF) goto leave_cki; if (!err && objlen > nnn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; } if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER) { /* Yep, this is the keyReference. Note: UL is currently not used. */ + (void)ul; for (ul=0; objlen; objlen--) { ul <<= 8; ul |= (*ppp++) & 0xff; nnn--; } } leave_cki: ; } /* Skip subClassAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class == CLASS_CONTEXT && tag == 0) { pp += objlen; nn -= objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); } /* Parse the keyAttributes. */ if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; /* Check that the reference is a Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) { errstr = "unsupported reference type"; goto parse_error; } pp += objlen; nn -= objlen; /* Parse the key size object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; keysize = 0; if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER && objlen == 2) { keysize = *pp++ << 8; keysize += *pp++; } /* Create a new PrKDF list item. */ prkdf = xtrycalloc (1, sizeof *prkdf); if (!prkdf) { err = gpg_error_from_syserror (); goto leave; } prkdf->keytype = keytype; prkdf->keysize = keysize; prkdf->objidlen = objidlen; prkdf->objid = xtrymalloc (objidlen); if (!prkdf->objid) { err = gpg_error_from_syserror (); xfree (prkdf); prkdf = NULL; goto leave; } memcpy (prkdf->objid, objid, objidlen); prkdf->usageflags = usageflags; prkdf->key_reference = fid & 0xFF; log_debug ("PrKDF %04hX: id=", fid); for (i=0; i < prkdf->objidlen; i++) log_printf ("%02X", prkdf->objid[i]); log_printf (" keyref=0x%02X", prkdf->key_reference); log_printf (" keysize=%zu", prkdf->keysize); log_printf (" usage="); s = ""; if (prkdf->usageflags.encrypt) { log_printf ("%sencrypt", s); s = ","; } if (prkdf->usageflags.decrypt) { log_printf ("%sdecrypt", s); s = ","; } if (prkdf->usageflags.sign) { log_printf ("%ssign", s); s = ","; } if (prkdf->usageflags.sign_recover) { log_printf ("%ssign_recover", s); s = ","; } if (prkdf->usageflags.wrap ) { log_printf ("%swrap", s); s = ","; } if (prkdf->usageflags.unwrap ) { log_printf ("%sunwrap", s); s = ","; } if (prkdf->usageflags.verify ) { log_printf ("%sverify", s); s = ","; } if (prkdf->usageflags.verify_recover) { log_printf ("%sverify_recover", s); s = ","; } if (prkdf->usageflags.derive ) { log_printf ("%sderive", s); s = ","; } if (prkdf->usageflags.non_repudiation) { log_printf ("%snon_repudiation", s); } log_printf ("\n"); xfree (buffer); buffer = NULL; buflen = 0; err = select_and_read_binary (app_get_slot (app), ((SC_HSM_EE_PREFIX << 8) | (fid & 0xFF)), "CertEF", &buffer, &buflen, 1); if (!err && buffer[0] == 0x30) { /* Create a matching CDF list item. */ cdf = xtrycalloc (1, sizeof *cdf); if (!cdf) { err = gpg_error_from_syserror (); goto leave; } cdf->objidlen = prkdf->objidlen; cdf->objid = xtrymalloc (cdf->objidlen); if (!cdf->objid) { err = gpg_error_from_syserror (); xfree (cdf); cdf = NULL; goto leave; } memcpy (cdf->objid, prkdf->objid, objidlen); cdf->fid = (SC_HSM_EE_PREFIX << 8) | (fid & 0xFF); log_debug ("CDF %04hX: id=", fid); for (i=0; i < cdf->objidlen; i++) log_printf ("%02X", cdf->objid[i]); log_printf (" fid=%04X\n", cdf->fid); } goto leave; /* Ready. */ parse_error: log_error ("error parsing PrKDF record (%d): %s - skipped\n", where, errstr? errstr : gpg_strerror (err)); err = 0; leave: xfree (buffer); if (err) { if (prkdf) { if (prkdf->objid) xfree (prkdf->objid); xfree (prkdf); } if (cdf) { if (cdf->objid) xfree (cdf->objid); xfree (cdf); } } else { if (prkdf) prkdf->next = *prkdresult; *prkdresult = prkdf; if (cdf) { cdf->next = *cdresult; *cdresult = cdf; } } return err; } /* Read and parse the Certificate Description File identified by FID. On success a the CDF list gets stored at RESULT and the caller is then responsible of releasing the object. Example data: 0000 30 35 30 11 0C 0B 43 65 72 74 69 66 69 63 61 74 050...Certificat 0010 65 03 02 06 40 30 16 04 14 C2 01 7C 2F BA A4 4A e...@0.....|/..J 0020 4A BB B8 49 11 DB 4A CA AA 7E 6A 2D 1B A1 08 30 J..I..J..~j-...0 0030 06 30 04 04 02 CA 00 .0..... Decoded example: SEQUENCE SIZE( 53 ) SEQUENCE SIZE( 17 ) UTF8-STRING SIZE( 11 ) -- label 0000 43 65 72 74 69 66 69 63 61 74 65 Certificate BIT-STRING SIZE( 2 ) -- common object attributes 0000 06 40 SEQUENCE SIZE( 22 ) OCTET-STRING SIZE( 20 ) -- id 0000 C2 01 7C 2F BA A4 4A 4A BB B8 49 11 DB 4A CA AA 0010 7E 6A 2D 1B A1 [ CONTEXT 1 ] IMPLICIT SEQUENCE SIZE( 8 ) SEQUENCE SIZE( 6 ) SEQUENCE SIZE( 4 ) OCTET-STRING SIZE( 2 ) -- path 0000 CA 00 .. */ static gpg_error_t read_ef_cd (app_t app, unsigned short fid, cdf_object_t *result) { gpg_error_t err; unsigned char *buffer = NULL; size_t buflen; const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, constructed, ndef; int i; const unsigned char *pp; size_t nn; int where; const char *errstr = NULL; cdf_object_t cdf = NULL; const unsigned char *objid; size_t objidlen; if (!fid) return gpg_error (GPG_ERR_NO_DATA); /* No certificates. */ err = select_and_read_binary (app_get_slot (app), fid, "CDF", &buffer, &buflen, 255); if (err) return err; p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { log_error ("error parsing CDF record: %s\n", gpg_strerror (err)); goto leave; } pp = p; nn = objlen; p += objlen; n -= objlen; /* Skip the commonObjectAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; pp += objlen; nn -= objlen; /* Parse the commonCertificateAttributes. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; { const unsigned char *ppp = pp; size_t nnn = objlen; pp += objlen; nn -= objlen; /* Get the Id. */ where = __LINE__; err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nnn || class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; objid = ppp; objidlen = objlen; } /* Parse the certAttribute. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; nn = objlen; /* Check that the reference is a Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE) { err = gpg_error (GPG_ERR_INV_OBJ); goto parse_error; } nn = objlen; /* Parse the Path object. */ where = __LINE__; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && objlen > nn) err = gpg_error (GPG_ERR_INV_OBJ); if (err) goto parse_error; /* Make sure that the next element is a non zero path and of even length (FID are two bytes each). */ if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING || (objlen & 1) ) { errstr = "invalid path reference"; goto parse_error; } /* Create a new CDF list item. */ cdf = xtrycalloc (1, sizeof *cdf); if (!cdf) { err = gpg_error_from_syserror (); goto leave; } cdf->objidlen = objidlen; cdf->objid = xtrymalloc (objidlen); if (!cdf->objid) { err = gpg_error_from_syserror (); xfree (cdf); cdf = NULL; goto leave; } memcpy (cdf->objid, objid, objidlen); cdf->fid = (SC_HSM_CA_PREFIX << 8) | (fid & 0xFF); log_debug ("CDF %04hX: id=", fid); for (i=0; i < cdf->objidlen; i++) log_printf ("%02X", cdf->objid[i]); goto leave; parse_error: log_error ("error parsing CDF record (%d): %s - skipped\n", where, errstr? errstr : gpg_strerror (err)); err = 0; leave: xfree (buffer); if (err) { if (cdf) { if (cdf->objid) xfree (cdf->objid); xfree (cdf); } } else { if (cdf) cdf->next = *result; *result = cdf; } return err; } /* Read the device certificate and extract the serial number. EF.C_DevAut (2F02) contains two CVCs, the first is the device certificate, the second is the issuer certificate. Example data: 0000 7F 21 81 E2 7F 4E 81 9B 5F 29 01 00 42 0B 55 54 .!...N.._)..B.UT 0010 43 43 30 32 30 30 30 30 32 7F 49 4F 06 0A 04 00 CC0200002.IO.... 0020 7F 00 07 02 02 02 02 03 86 41 04 6D FF D6 85 57 .........A.m...W 0030 40 FB 10 5D 94 71 8A 94 D2 5E 50 33 E7 1E C0 6C @..].q...^P3...l 0040 63 D5 C8 FC BA F3 02 1D 70 23 F6 47 E8 35 48 EF c.......p#.G.5H. 0050 B5 94 72 3C 6F BE C0 EB 9A C7 FB 06 59 26 CF 65 ..r...<. 0150 6B AC 06 EA 5F 20 0B 55 54 43 43 30 32 30 30 30 k..._ .UTCC02000 0160 30 32 7F 4C 10 06 0B 2B 06 01 04 01 81 C3 1F 03 02.L...+........ 0170 01 01 53 01 80 5F 25 06 01 03 00 03 02 08 5F 24 ..S.._%......._$ 0180 06 02 01 00 03 02 07 5F 37 40 93 C1 42 8B B3 8E ......._7@..B... 0190 42 61 6F 2C 19 E6 98 41 BD AA 60 BD E0 DD 4E F0 Bao,...A..`...N. 01A0 15 D5 4F 71 B7 BB C3 3A F2 AD 27 5E DD EE 6D 12 ..Oq...:..'^..m. 01B0 76 E6 2B A0 4C 01 CA C1 26 0C 45 6D C6 CB EC 92 v.+.L...&.Em.... 01C0 BF 38 18 AD 8F B2 29 40 A9 51 .8....)@.Q The certificate format is defined in BSI TR-03110: 7F21 [ APPLICATION 33 ] IMPLICIT SEQUENCE SIZE( 226 ) 7F4E [ APPLICATION 78 ] IMPLICIT SEQUENCE SIZE( 155 ) 5F29 [ APPLICATION 41 ] SIZE( 1 ) -- profile id 0000 00 42 [ APPLICATION 2 ] SIZE( 11 ) -- CAR 0000 55 54 43 43 30 32 30 30 30 30 32 UTCC0200002 7F49 [ APPLICATION 73 ] IMPLICIT SEQUENCE SIZE( 79 ) -- public key OBJECT IDENTIFIER = { id-TA-ECDSA-SHA-256 } 86 [ CONTEXT 6 ] SIZE( 65 ) 0000 04 6D FF D6 85 57 40 FB 10 5D 94 71 8A 94 D2 5E 0010 50 33 E7 1E C0 6C 63 D5 C8 FC BA F3 02 1D 70 23 0020 F6 47 E8 35 48 EF B5 94 72 3C 6F BE C0 EB 9A C7 0030 FB 06 59 26 CF 65 EF A1 72 E0 98 F3 F0 44 1B B7 0040 71 5F20 [ APPLICATION 32 ] SIZE( 16 ) -- CHR 0000 55 54 43 43 30 32 30 30 30 31 33 30 30 30 30 30 UTCC020001300000 7F4C [ APPLICATION 76 ] IMPLICIT SEQUENCE SIZE( 16 ) -- CHAT OBJECT IDENTIFIER = { 1 3 6 1 4 1 24991 3 1 1 } 53 [ APPLICATION 19 ] SIZE( 1 ) 0000 00 5F25 [ APPLICATION 37 ] SIZE( 6 ) -- Valid from 0000 01 04 00 07 01 01 5F24 [ APPLICATION 36 ] SIZE( 6 ) -- Valid to 0000 02 01 00 03 02 07 5F37 [ APPLICATION 55 ] SIZE( 64 ) -- Signature 0000 7F 73 04 3B 06 63 79 41 BE 1A 9F FC F6 77 67 2B 0010 8A 41 D1 11 F6 9B 54 44 AD 19 FB B8 0C C6 2F 34 0020 71 8E 4F F6 92 59 34 61 D9 4F 4A 86 36 A8 D8 9A 0030 C6 3C 17 7E 71 CE A8 26 D0 C5 25 61 78 9D 01 F8 The serial number is contained in tag 5F20, while the last 5 digits are truncated. */ static gpg_error_t read_serialno(app_t app) { gpg_error_t err; unsigned char *buffer = NULL; size_t buflen; const unsigned char *p,*chr; size_t n, objlen, hdrlen, chrlen; int class, tag, constructed, ndef; err = select_and_read_binary (app_get_slot (app), 0x2F02, "EF.C_DevAut", &buffer, &buflen, 512); if (err) return err; p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != 0x21)) err = gpg_error (GPG_ERR_INV_OBJ); if (err) { log_error ("error parsing C_DevAut: %s\n", gpg_strerror (err)); goto leave; } chr = find_tlv (p, objlen, 0x5F20, &chrlen); if (!chr || chrlen <= 5) { err = gpg_error (GPG_ERR_INV_OBJ); log_error ("CHR not found in CVC\n"); goto leave; } chrlen -= 5; app->card->serialno = xtrymalloc (chrlen); if (!app->card->serialno) { err = gpg_error_from_syserror (); goto leave; } app->card->serialnolen = chrlen; memcpy (app->card->serialno, chr, chrlen); leave: xfree (buffer); return err; } /* Get all the basic information from the SmartCard-HSM, check the structure and initialize our local context. This is used once at application initialization. */ static gpg_error_t read_meta (app_t app) { gpg_error_t err; unsigned char *eflist = NULL; size_t eflistlen = 0; int i; err = read_serialno(app); if (err) return err; err = list_ef (app_get_slot (app), &eflist, &eflistlen); if (err) return err; for (i = 0; i < eflistlen; i += 2) { switch(eflist[i]) { case SC_HSM_KEY_PREFIX: if (eflist[i + 1] == 0) /* No key with ID=0 */ break; err = read_ef_prkd (app, ((SC_HSM_PRKD_PREFIX << 8) | eflist[i + 1]), &app->app_local->private_key_info, &app->app_local->certificate_info); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; if (err) return err; break; case SC_HSM_CD_PREFIX: err = read_ef_cd (app, ((eflist[i] << 8) | eflist[i + 1]), &app->app_local->trusted_certificate_info); if (gpg_err_code (err) == GPG_ERR_NO_DATA) err = 0; if (err) return err; break; } } xfree (eflist); return err; } /* Helper to do_learn_status: Send information about all certificates listed in CERTINFO back. Use CERTTYPE as type of the certificate. */ static gpg_error_t send_certinfo (ctrl_t ctrl, const char *certtype, cdf_object_t certinfo) { for (; certinfo; certinfo = certinfo->next) { char *buf, *p; buf = xtrymalloc (4 + certinfo->objidlen*2 + 1); if (!buf) return gpg_error_from_syserror (); p = stpcpy (buf, "HSM."); bin2hex (certinfo->objid, certinfo->objidlen, p); send_status_info (ctrl, "CERTINFO", certtype, strlen (certtype), buf, strlen (buf), NULL, (size_t)0); xfree (buf); } return 0; } /* Get the keygrip of the private key object PRKDF. On success the keygrip gets returned in the caller provided 41 byte buffer R_GRIPSTR. */ static gpg_error_t keygripstr_from_prkdf (app_t app, prkdf_object_t prkdf, char *r_gripstr) { gpg_error_t err; cdf_object_t cdf; unsigned char *der; size_t derlen; ksba_cert_t cert; /* Look for a matching certificate. A certificate matches if the Id matches the one of the private key info. */ for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next) if (cdf->objidlen == prkdf->objidlen && !memcmp (cdf->objid, prkdf->objid, prkdf->objidlen)) break; if (!cdf) return gpg_error (GPG_ERR_NOT_FOUND); err = readcert_by_cdf (app, cdf, &der, &derlen); if (err) return err; err = ksba_cert_new (&cert); if (!err) err = ksba_cert_init_from_mem (cert, der, derlen); xfree (der); if (!err) err = app_help_get_keygrip_string (cert, r_gripstr, NULL, NULL); ksba_cert_release (cert); return err; } /* Helper to do_learn_status: Send information about all known keypairs back. */ static gpg_error_t send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t keyinfo) { gpg_error_t err; for (; keyinfo; keyinfo = keyinfo->next) { char gripstr[40+1]; char *buf, *p; buf = xtrymalloc (4 + keyinfo->objidlen*2 + 1); if (!buf) return gpg_error_from_syserror (); p = stpcpy (buf, "HSM."); bin2hex (keyinfo->objid, keyinfo->objidlen, p); err = keygripstr_from_prkdf (app, keyinfo, gripstr); if (err) { log_error ("can't get keygrip from %04X\n", keyinfo->key_reference); } else { log_assert (strlen (gripstr) == 40); send_status_info (ctrl, "KEYPAIRINFO", gripstr, 40, buf, strlen (buf), NULL, (size_t)0); } xfree (buf); } return 0; } /* This is the handler for the LEARN command. */ static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) { gpg_error_t err; if ((flags & APP_LEARN_FLAG_KEYPAIRINFO)) err = 0; else { err = send_certinfo (ctrl, "100", app->app_local->certificate_info); if (!err) err = send_certinfo (ctrl, "101", app->app_local->trusted_certificate_info); } if (!err) err = send_keypairinfo (app, ctrl, app->app_local->private_key_info); return err; } /* Read a certificate using the information in CDF and return the certificate in a newly allocated buffer R_CERT and its length R_CERTLEN. */ static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf, unsigned char **r_cert, size_t *r_certlen) { gpg_error_t err; unsigned char *buffer = NULL; const unsigned char *p, *save_p; size_t buflen, n; int class, tag, constructed, ndef; size_t totobjlen, objlen, hdrlen; int rootca; int i; *r_cert = NULL; *r_certlen = 0; /* First check whether it has been cached. */ if (cdf->image) { *r_cert = xtrymalloc (cdf->imagelen); if (!*r_cert) return gpg_error_from_syserror (); memcpy (*r_cert, cdf->image, cdf->imagelen); *r_certlen = cdf->imagelen; return 0; } err = select_and_read_binary (app_get_slot (app), cdf->fid, "CD", &buffer, &buflen, 4096); if (err) { log_error ("error reading certificate with Id "); for (i=0; i < cdf->objidlen; i++) log_printf ("%02X", cdf->objid[i]); log_printf (": %s\n", gpg_strerror (err)); goto leave; } /* Check whether this is really a certificate. */ p = buffer; n = buflen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) rootca = 0; else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed ) rootca = 1; else { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } totobjlen = objlen + hdrlen; log_assert (totobjlen <= buflen); err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if (!rootca && class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed) { /* The certificate seems to be contained in a userCertificate container. Skip this and assume the following sequence is the certificate. */ if (n < objlen) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } p += objlen; n -= objlen; save_p = p; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (err) goto leave; if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) ) { err = gpg_error (GPG_ERR_INV_OBJ); goto leave; } totobjlen = objlen + hdrlen; log_assert (save_p + totobjlen <= buffer + buflen); memmove (buffer, save_p, totobjlen); } *r_cert = buffer; buffer = NULL; *r_certlen = totobjlen; /* Try to cache it. */ if (!cdf->image && (cdf->image = xtrymalloc (*r_certlen))) { memcpy (cdf->image, *r_cert, *r_certlen); cdf->imagelen = *r_certlen; } leave: xfree (buffer); return err; } /* Handler for the READCERT command. Read the certificate with id CERTID (as returned by learn_status in the CERTINFO status lines) and return it in the freshly allocated buffer to be stored at R_CERT and its length at R_CERTLEN. A error code will be returned on failure and R_CERT and R_CERTLEN will be set to (NULL,0). */ static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **r_cert, size_t *r_certlen) { gpg_error_t err; cdf_object_t cdf; *r_cert = NULL; *r_certlen = 0; err = cdf_object_from_certid (app, certid, &cdf); if (!err) err = readcert_by_cdf (app, cdf, r_cert, r_certlen); return err; } /* Implement the GETATTR command. This is similar to the LEARN command but returns just one value via the status interface. */ static gpg_error_t do_getattr (app_t app, ctrl_t ctrl, const char *name) { if (!strcmp (name, "$AUTHKEYID")) { char *buf, *p; prkdf_object_t prkdf; /* We return the ID of the first private key capable of signing. */ for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next) if (prkdf->usageflags.sign) break; if (prkdf) { buf = xtrymalloc (4 + prkdf->objidlen*2 + 1); if (!buf) return gpg_error_from_syserror (); p = stpcpy (buf, "HSM."); bin2hex (prkdf->objid, prkdf->objidlen, p); send_status_info (ctrl, name, buf, strlen (buf), NULL, 0); xfree (buf); return 0; } } else if (!strcmp (name, "$DISPSERIALNO")) { send_status_info (ctrl, name, app->card->serialno, app->card->serialnolen, NULL, 0); return 0; } return gpg_error (GPG_ERR_INV_NAME); } /* Apply PKCS#1 V1.5 padding for signature operation. The function * combines padding, digest info and the hash value. The buffer must * be allocated by the caller matching the key size. */ static void apply_PKCS_padding(const unsigned char *dig, int diglen, const unsigned char *prefix, int prefixlen, unsigned char *buff, int bufflen) { int i, n_ff; /* Caller must ensure a sufficient buffer. */ if (diglen + prefixlen + 4 > bufflen) return; n_ff = bufflen - diglen - prefixlen - 3; *buff++ = 0x00; *buff++ = 0x01; for (i=0; i < n_ff; i++) *buff++ = 0xFF; *buff++ = 0x00; if (prefix) memcpy (buff, prefix, prefixlen); buff += prefixlen; memcpy (buff, dig, diglen); } /* Decode a digest info structure (DI,DILEN) to extract the hash * value. The buffer HASH to receive the digest must be provided by * the caller with HASHLEN pointing to the inbound length. HASHLEN is * updated to the outbound length. */ static int hash_from_digestinfo (const unsigned char *di, size_t dilen, unsigned char *hash, size_t *hashlen) { const unsigned char *p,*pp; size_t n, nn, objlen, hdrlen; int class, tag, constructed, ndef; gpg_error_t err; p = di; n = dilen; err = parse_ber_header (&p, &n, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > n || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if ( err ) return err; pp = p; nn = objlen; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_SEQUENCE)) err = gpg_error (GPG_ERR_INV_OBJ); if ( err ) return err; pp += objlen; nn -= objlen; err = parse_ber_header (&pp, &nn, &class, &tag, &constructed, &ndef, &objlen, &hdrlen); if (!err && (objlen > nn || tag != TAG_OCTET_STRING)) err = gpg_error (GPG_ERR_INV_OBJ); if ( err ) return err; if (*hashlen < objlen) return gpg_error (GPG_ERR_TOO_SHORT); memcpy (hash, pp, objlen); *hashlen = objlen; return 0; } /* Perform PIN verification */ static gpg_error_t verify_pin (app_t app, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; pininfo_t pininfo; char *pinvalue; char *prompt; int sw; sw = apdu_send_simple (app_get_slot (app), 0, 0x00, ISO7816_VERIFY, 0x00, 0x81, -1, NULL); if (sw == SW_SUCCESS) return 0; /* PIN already verified */ if (sw == SW_REF_DATA_INV) { log_error ("SmartCard-HSM not initialized. Run sc-hsm-tool first\n"); return gpg_error (GPG_ERR_NO_PIN); } if (sw == SW_CHV_BLOCKED) { log_error ("PIN Blocked\n"); return gpg_error (GPG_ERR_PIN_BLOCKED); } memset (&pininfo, 0, sizeof pininfo); pininfo.fixedlen = 0; pininfo.minlen = 6; pininfo.maxlen = 15; prompt = "||Please enter the PIN"; if (!opt.disable_pinpad && !iso7816_check_pinpad (app_get_slot (app), ISO7816_VERIFY, &pininfo) ) { err = pincb (pincb_arg, prompt, NULL); if (err) { log_info ("PIN callback returned error: %s\n", gpg_strerror (err)); return err; } err = iso7816_verify_kp (app_get_slot (app), 0x81, &pininfo); pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */ } else { err = pincb (pincb_arg, prompt, &pinvalue); if (err) { log_info ("PIN callback returned error: %s\n", gpg_strerror (err)); return err; } err = iso7816_verify (app_get_slot (app), 0x81, pinvalue, strlen(pinvalue)); xfree (pinvalue); } if (err) { log_error ("PIN verification failed: %s\n", gpg_strerror (err)); return err; } log_debug ("PIN verification succeeded\n"); return err; } /* Handler for the PKSIGN command. Create the signature and return the allocated result in OUTDATA. If a PIN is required, the PINCB will be used to ask for the PIN; that callback should return the PIN in an allocated buffer and store that as the 3rd argument. The API is somewhat inconsistent: The caller can either supply a plain hash and the algorithm in hashalgo or a complete DigestInfo structure. The former is detect by characteristic length of the provided data (20,28,32,48 or 64 byte). The function returns the RSA block in the size of the modulus or the ECDSA signature in X9.62 format (SEQ/INT(r)/INT(s)) */ static gpg_error_t do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */ { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1C }; static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */ { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 }; static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */ { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30 }; static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */ { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 }; gpg_error_t err; unsigned char cdsblk[256]; /* Raw PKCS#1 V1.5 block with padding (RSA) or hash. */ prkdf_object_t prkdf; /* The private key object. */ size_t cdsblklen; unsigned char algoid; int sw; (void)ctrl; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); if (indatalen > 124) /* Limit for 1024 bit key */ return gpg_error (GPG_ERR_INV_VALUE); err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); if (err) return err; if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover ||prkdf->usageflags.non_repudiation)) { log_error ("key %s may not be used for signing\n", keyidstr); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if (prkdf->keytype == KEY_TYPE_RSA) { algoid = 0x20; cdsblklen = prkdf->keysize >> 3; if (!cdsblklen) cdsblklen = 256; if (hashalgo == GCRY_MD_SHA1 && indatalen == 20) apply_PKCS_padding (indata, indatalen, sha1_prefix, sizeof(sha1_prefix), cdsblk, cdsblklen); else if (hashalgo == GCRY_MD_MD5 && indatalen == 20) apply_PKCS_padding (indata, indatalen, rmd160_prefix, sizeof(rmd160_prefix), cdsblk, cdsblklen); else if (hashalgo == GCRY_MD_SHA224 && indatalen == 28) apply_PKCS_padding (indata, indatalen, sha224_prefix, sizeof(sha224_prefix), cdsblk, cdsblklen); else if (hashalgo == GCRY_MD_SHA256 && indatalen == 32) apply_PKCS_padding (indata, indatalen, sha256_prefix, sizeof(sha256_prefix), cdsblk, cdsblklen); else if (hashalgo == GCRY_MD_SHA384 && indatalen == 48) apply_PKCS_padding (indata, indatalen, sha384_prefix, sizeof(sha384_prefix), cdsblk, cdsblklen); else if (hashalgo == GCRY_MD_SHA512 && indatalen == 64) apply_PKCS_padding (indata, indatalen, sha512_prefix, sizeof(sha512_prefix), cdsblk, cdsblklen); else /* Assume it's already a digest info or TLS_MD5SHA1 */ apply_PKCS_padding (indata, indatalen, NULL, 0, cdsblk, cdsblklen); } else { algoid = 0x70; if (indatalen != 20 && indatalen != 28 && indatalen != 32 && indatalen != 48 && indatalen != 64) { cdsblklen = sizeof(cdsblk); err = hash_from_digestinfo (indata, indatalen, cdsblk, &cdsblklen); if (err) { log_error ("DigestInfo invalid: %s\n", gpg_strerror (err)); return err; } } else { memcpy (cdsblk, indata, indatalen); cdsblklen = indatalen; } } err = verify_pin (app, pincb, pincb_arg); if (err) return err; sw = apdu_send_le (app_get_slot (app), 1, 0x80, 0x68, prkdf->key_reference, algoid, cdsblklen, cdsblk, 0, outdata, outdatalen); return iso7816_map_sw (sw); } /* Handler for the PKAUTH command. This is basically the same as the PKSIGN command but we first check that the requested key is suitable for authentication; that is, it must match the criteria used for the attribute $AUTHKEYID. See do_sign for calling conventions; there is no HASHALGO, though. */ static gpg_error_t do_auth (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; prkdf_object_t prkdf; int algo; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); if (err) return err; if (!prkdf->usageflags.sign) { log_error ("key %s may not be used for authentication\n", keyidstr); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1; return do_sign (app, ctrl, keyidstr, algo, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); } /* Check PKCS#1 V1.5 padding and extract plain text. The function * allocates a buffer for the plain text. The caller must release the * buffer. */ static gpg_error_t strip_PKCS15_padding(unsigned char *src, int srclen, unsigned char **dst, size_t *dstlen) { unsigned char *p; if (srclen < 2) return gpg_error (GPG_ERR_DECRYPT_FAILED); if (*src++ != 0x00) return gpg_error (GPG_ERR_DECRYPT_FAILED); if (*src++ != 0x02) return gpg_error (GPG_ERR_DECRYPT_FAILED); srclen -= 2; while ((srclen > 0) && *src) { src++; srclen--; } if (srclen < 2) return gpg_error (GPG_ERR_DECRYPT_FAILED); src++; srclen--; p = xtrymalloc (srclen); if (!p) return gpg_error_from_syserror (); memcpy (p, src, srclen); *dst = p; *dstlen = srclen; return 0; } /* Decrypt a PKCS#1 V1.5 formatted cryptogram using the referenced key. */ static gpg_error_t do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen, unsigned int *r_info) { gpg_error_t err; unsigned char p1blk[256]; /* Enciphered P1 block */ prkdf_object_t prkdf; /* The private key object. */ unsigned char *rspdata; size_t rspdatalen; size_t p1blklen; int sw; (void)ctrl; if (!keyidstr || !*keyidstr || !indatalen) return gpg_error (GPG_ERR_INV_VALUE); err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf); if (err) return err; if (!(prkdf->usageflags.decrypt || prkdf->usageflags.unwrap)) { log_error ("key %s may not be used for deciphering\n", keyidstr); return gpg_error (GPG_ERR_WRONG_KEY_USAGE); } if (prkdf->keytype != KEY_TYPE_RSA) return gpg_error (GPG_ERR_NOT_SUPPORTED); p1blklen = prkdf->keysize >> 3; if (!p1blklen) p1blklen = 256; /* The input may be shorter (due to MPIs not storing leading zeroes) or longer than the block size. We put INDATA right aligned into the buffer. If INDATA is longer than the block size we truncate it on the left. */ memset (p1blk, 0, sizeof(p1blk)); if (indatalen > p1blklen) memcpy (p1blk, (unsigned char *)indata + (indatalen - p1blklen), p1blklen); else memcpy (p1blk + (p1blklen - indatalen), indata, indatalen); err = verify_pin(app, pincb, pincb_arg); if (err) return err; sw = apdu_send_le (app_get_slot (app), 1, 0x80, 0x62, prkdf->key_reference, 0x21, p1blklen, p1blk, 0, &rspdata, &rspdatalen); err = iso7816_map_sw (sw); if (err) { log_error ("Decrypt failed: %s\n", gpg_strerror (err)); return err; } err = strip_PKCS15_padding (rspdata, rspdatalen, outdata, outdatalen); xfree (rspdata); if (!err) *r_info |= APP_DECIPHER_INFO_NOPAD; return err; } /* * Select the SmartCard-HSM application on the card in SLOT. */ gpg_error_t app_select_sc_hsm (app_t app) { int slot = app_get_slot (app); int rc; rc = iso7816_select_application (slot, sc_hsm_aid, sizeof sc_hsm_aid, 0); if (!rc) { app->apptype = APPTYPE_SC_HSM; app->app_local = xtrycalloc (1, sizeof *app->app_local); if (!app->app_local) { rc = gpg_error_from_syserror (); goto leave; } rc = read_meta (app); if (rc) goto leave; app->fnc.deinit = do_deinit; app->fnc.prep_reselect = NULL; app->fnc.reselect = NULL; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.getattr = do_getattr; app->fnc.setattr = NULL; app->fnc.genkey = NULL; app->fnc.sign = do_sign; app->fnc.auth = do_auth; app->fnc.decipher = do_decipher; app->fnc.change_pin = NULL; app->fnc.check_pin = NULL; leave: if (rc) do_deinit (app); } return rc; } diff --git a/tests/gpgscm/scheme.c b/tests/gpgscm/scheme.c index a8191657d..bde39fcd0 100644 --- a/tests/gpgscm/scheme.c +++ b/tests/gpgscm/scheme.c @@ -1,6029 +1,6029 @@ /* T I N Y S C H E M E 1 . 4 1 * Dimitrios Souflis (dsouflis@acm.org) * Based on MiniScheme (original credits follow) * (MINISCM) coded by Atsushi Moriwaki (11/5/1989) * (MINISCM) E-MAIL : moriwaki@kurims.kurims.kyoto-u.ac.jp * (MINISCM) This version has been modified by R.C. Secrist. * (MINISCM) * (MINISCM) Mini-Scheme is now maintained by Akira KIDA. * (MINISCM) * (MINISCM) This is a revised and modified version by Akira KIDA. * (MINISCM) current version is 0.85k4 (15 May 1994) * */ #ifdef HAVE_CONFIG_H # include #endif #define _SCHEME_SOURCE #include "scheme-private.h" #ifndef WIN32 # include #endif #ifdef WIN32 #define snprintf _snprintf #endif #if USE_DL # include "dynload.h" #endif #if USE_MATH # include #endif #include #include #include #include #include #if USE_STRCASECMP #include # ifndef __APPLE__ # define stricmp strcasecmp # endif #endif /* Used for documentation purposes, to signal functions in 'interface' */ #define INTERFACE #define TOK_EOF (-1) #define TOK_LPAREN 0 #define TOK_RPAREN 1 #define TOK_DOT 2 #define TOK_ATOM 3 #define TOK_QUOTE 4 #define TOK_COMMENT 5 #define TOK_DQUOTE 6 #define TOK_BQUOTE 7 #define TOK_COMMA 8 #define TOK_ATMARK 9 #define TOK_SHARP 10 #define TOK_SHARP_CONST 11 #define TOK_VEC 12 #define BACKQUOTE '`' #define DELIMITERS "()\";\f\t\v\n\r " /* * Basic memory allocation units */ #define banner "TinyScheme 1.41" #include #include #include #ifdef __APPLE__ static int stricmp(const char *s1, const char *s2) { unsigned char c1, c2; do { c1 = tolower(*s1); c2 = tolower(*s2); if (c1 < c2) return -1; else if (c1 > c2) return 1; s1++, s2++; } while (c1 != 0); return 0; } #endif /* __APPLE__ */ #if USE_STRLWR && !defined(HAVE_STRLWR) static const char *strlwr(char *s) { const char *p=s; while(*s) { *s=tolower(*s); s++; } return p; } #endif #ifndef prompt # define prompt "ts> " #endif #ifndef InitFile # define InitFile "init.scm" #endif #ifndef FIRST_CELLSEGS # define FIRST_CELLSEGS 3 #endif /* All types have the LSB set. The garbage collector takes advantage * of that to identify types. */ enum scheme_types { T_STRING = 1 << 1 | 1, T_NUMBER = 2 << 1 | 1, T_SYMBOL = 3 << 1 | 1, T_PROC = 4 << 1 | 1, T_PAIR = 5 << 1 | 1, T_CLOSURE = 6 << 1 | 1, T_CONTINUATION = 7 << 1 | 1, T_FOREIGN = 8 << 1 | 1, T_CHARACTER = 9 << 1 | 1, T_PORT = 10 << 1 | 1, T_VECTOR = 11 << 1 | 1, T_MACRO = 12 << 1 | 1, T_PROMISE = 13 << 1 | 1, T_ENVIRONMENT = 14 << 1 | 1, T_FOREIGN_OBJECT = 15 << 1 | 1, T_BOOLEAN = 16 << 1 | 1, T_NIL = 17 << 1 | 1, T_EOF_OBJ = 18 << 1 | 1, T_SINK = 19 << 1 | 1, T_FRAME = 20 << 1 | 1, T_LAST_SYSTEM_TYPE = 20 << 1 | 1 }; static const char * type_to_string (enum scheme_types typ) { switch (typ) { case T_STRING: return "string"; case T_NUMBER: return "number"; case T_SYMBOL: return "symbol"; case T_PROC: return "proc"; case T_PAIR: return "pair"; case T_CLOSURE: return "closure"; case T_CONTINUATION: return "continuation"; case T_FOREIGN: return "foreign"; case T_CHARACTER: return "character"; case T_PORT: return "port"; case T_VECTOR: return "vector"; case T_MACRO: return "macro"; case T_PROMISE: return "promise"; case T_ENVIRONMENT: return "environment"; case T_FOREIGN_OBJECT: return "foreign object"; case T_BOOLEAN: return "boolean"; case T_NIL: return "nil"; case T_EOF_OBJ: return "eof object"; case T_SINK: return "sink"; case T_FRAME: return "frame"; } assert (! "not reached"); } /* ADJ is enough slack to align cells in a TYPE_BITS-bit boundary */ #define TYPE_BITS 6 #define ADJ (1 << TYPE_BITS) #define T_MASKTYPE (ADJ - 1) /* 0000000000111111 */ #define T_TAGGED 1024 /* 0000010000000000 */ #define T_FINALIZE 2048 /* 0000100000000000 */ #define T_SYNTAX 4096 /* 0001000000000000 */ #define T_IMMUTABLE 8192 /* 0010000000000000 */ #define T_ATOM 16384 /* 0100000000000000 */ /* only for gc */ #define CLRATOM 49151 /* 1011111111111111 */ /* only for gc */ #define MARK 32768 /* 1000000000000000 */ #define UNMARK 32767 /* 0111111111111111 */ static num num_add(num a, num b); static num num_mul(num a, num b); static num num_div(num a, num b); static num num_intdiv(num a, num b); static num num_sub(num a, num b); static num num_rem(num a, num b); static num num_mod(num a, num b); static int num_eq(num a, num b); static int num_gt(num a, num b); static int num_ge(num a, num b); static int num_lt(num a, num b); static int num_le(num a, num b); #if USE_MATH static double round_per_R5RS(double x); #endif static int is_zero_double(double x); static INLINE int num_is_integer(pointer p) { return ((p)->_object._number.is_fixnum); } static const struct num num_zero = { 1, {0} }; static const struct num num_one = { 1, {1} }; /* macros for cell operations */ #define typeflag(p) ((p)->_flag) #define type(p) (typeflag(p)&T_MASKTYPE) #define settype(p, typ) (typeflag(p) = (typeflag(p) & ~T_MASKTYPE) | (typ)) INTERFACE INLINE int is_string(pointer p) { return (type(p)==T_STRING); } #define strvalue(p) ((p)->_object._string._svalue) #define strlength(p) ((p)->_object._string._length) INTERFACE static int is_list(scheme *sc, pointer p); INTERFACE INLINE int is_vector(pointer p) { return (type(p)==T_VECTOR); } /* Given a vector, return it's length. */ #define vector_length(v) (v)->_object._vector._length /* Given a vector length, compute the amount of cells required to * represent it. */ #define vector_size(len) (1 + ((len) - 1 + 2) / 3) INTERFACE static void fill_vector(pointer vec, pointer obj); INTERFACE static pointer *vector_elem_slot(pointer vec, int ielem); INTERFACE static pointer vector_elem(pointer vec, int ielem); INTERFACE static pointer set_vector_elem(pointer vec, int ielem, pointer a); INTERFACE INLINE int is_number(pointer p) { return (type(p)==T_NUMBER); } INTERFACE INLINE int is_integer(pointer p) { if (!is_number(p)) return 0; if (num_is_integer(p) || (double)ivalue(p) == rvalue(p)) return 1; return 0; } INTERFACE INLINE int is_real(pointer p) { return is_number(p) && (!(p)->_object._number.is_fixnum); } INTERFACE INLINE int is_character(pointer p) { return (type(p)==T_CHARACTER); } INTERFACE INLINE char *string_value(pointer p) { return strvalue(p); } INLINE num nvalue(pointer p) { return ((p)->_object._number); } INTERFACE long ivalue(pointer p) { return (num_is_integer(p)?(p)->_object._number.value.ivalue:(long)(p)->_object._number.value.rvalue); } INTERFACE double rvalue(pointer p) { return (!num_is_integer(p)?(p)->_object._number.value.rvalue:(double)(p)->_object._number.value.ivalue); } #define ivalue_unchecked(p) ((p)->_object._number.value.ivalue) #define rvalue_unchecked(p) ((p)->_object._number.value.rvalue) #define set_num_integer(p) (p)->_object._number.is_fixnum=1; #define set_num_real(p) (p)->_object._number.is_fixnum=0; INTERFACE long charvalue(pointer p) { return ivalue_unchecked(p); } INTERFACE INLINE int is_port(pointer p) { return (type(p)==T_PORT); } INTERFACE INLINE int is_inport(pointer p) { return is_port(p) && p->_object._port->kind & port_input; } INTERFACE INLINE int is_outport(pointer p) { return is_port(p) && p->_object._port->kind & port_output; } INTERFACE INLINE int is_pair(pointer p) { return (type(p)==T_PAIR); } #define car(p) ((p)->_object._cons._car) #define cdr(p) ((p)->_object._cons._cdr) INTERFACE pointer pair_car(pointer p) { return car(p); } INTERFACE pointer pair_cdr(pointer p) { return cdr(p); } INTERFACE pointer set_car(pointer p, pointer q) { return car(p)=q; } INTERFACE pointer set_cdr(pointer p, pointer q) { return cdr(p)=q; } INTERFACE INLINE int is_symbol(pointer p) { return (type(p)==T_SYMBOL); } INTERFACE INLINE char *symname(pointer p) { return strvalue(car(p)); } #if USE_PLIST SCHEME_EXPORT INLINE int hasprop(pointer p) { return (is_symbol(p)); } #define symprop(p) cdr(p) #endif INTERFACE INLINE int is_syntax(pointer p) { return (typeflag(p)&T_SYNTAX); } INTERFACE INLINE int is_proc(pointer p) { return (type(p)==T_PROC); } INTERFACE INLINE int is_foreign(pointer p) { return (type(p)==T_FOREIGN); } INTERFACE INLINE char *syntaxname(pointer p) { return strvalue(car(p)); } #define procnum(p) ivalue_unchecked(p) static const char *procname(pointer x); INTERFACE INLINE int is_closure(pointer p) { return (type(p)==T_CLOSURE); } INTERFACE INLINE int is_macro(pointer p) { return (type(p)==T_MACRO); } INTERFACE INLINE pointer closure_code(pointer p) { return car(p); } INTERFACE INLINE pointer closure_env(pointer p) { return cdr(p); } INTERFACE INLINE int is_continuation(pointer p) { return (type(p)==T_CONTINUATION); } #define cont_dump(p) cdr(p) INTERFACE INLINE int is_foreign_object(pointer p) { return (type(p)==T_FOREIGN_OBJECT); } INTERFACE const foreign_object_vtable *get_foreign_object_vtable(pointer p) { return p->_object._foreign_object._vtable; } INTERFACE void *get_foreign_object_data(pointer p) { return p->_object._foreign_object._data; } /* To do: promise should be forced ONCE only */ INTERFACE INLINE int is_promise(pointer p) { return (type(p)==T_PROMISE); } INTERFACE INLINE int is_environment(pointer p) { return (type(p)==T_ENVIRONMENT); } #define setenvironment(p) typeflag(p) = T_ENVIRONMENT INTERFACE INLINE int is_frame(pointer p) { return (type(p) == T_FRAME); } #define setframe(p) settype(p, T_FRAME) #define is_atom(p) (typeflag(p)&T_ATOM) #define setatom(p) typeflag(p) |= T_ATOM #define clratom(p) typeflag(p) &= CLRATOM #define is_mark(p) (typeflag(p)&MARK) #define setmark(p) typeflag(p) |= MARK #define clrmark(p) typeflag(p) &= UNMARK INTERFACE INLINE int is_immutable(pointer p) { return (typeflag(p)&T_IMMUTABLE); } /*#define setimmutable(p) typeflag(p) |= T_IMMUTABLE*/ INTERFACE INLINE void setimmutable(pointer p) { typeflag(p) |= T_IMMUTABLE; } #define caar(p) car(car(p)) #define cadr(p) car(cdr(p)) #define cdar(p) cdr(car(p)) #define cddr(p) cdr(cdr(p)) #define cadar(p) car(cdr(car(p))) #define caddr(p) car(cdr(cdr(p))) #define cdaar(p) cdr(car(car(p))) #define cadaar(p) car(cdr(car(car(p)))) #define cadddr(p) car(cdr(cdr(cdr(p)))) #define cddddr(p) cdr(cdr(cdr(cdr(p)))) #if USE_HISTORY static pointer history_flatten(scheme *sc); static void history_mark(scheme *sc); #else # define history_mark(SC) (void) 0 # define history_flatten(SC) (SC)->NIL #endif #if USE_CHAR_CLASSIFIERS static INLINE int Cisalpha(int c) { return isascii(c) && isalpha(c); } static INLINE int Cisdigit(int c) { return isascii(c) && isdigit(c); } static INLINE int Cisspace(int c) { return isascii(c) && isspace(c); } static INLINE int Cisupper(int c) { return isascii(c) && isupper(c); } static INLINE int Cislower(int c) { return isascii(c) && islower(c); } #endif #if USE_ASCII_NAMES static const char charnames[32][3]={ "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", "bs", "ht", "lf", "vt", "ff", "cr", "so", "si", "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb", "can", "em", "sub", "esc", "fs", "gs", "rs", "us" }; static int is_ascii_name(const char *name, int *pc) { int i; for(i=0; i<32; i++) { if (strncasecmp(name, charnames[i], 3) == 0) { *pc=i; return 1; } } if (strcasecmp(name, "del") == 0) { *pc=127; return 1; } return 0; } #endif static int file_push(scheme *sc, pointer fname); static void file_pop(scheme *sc); static int file_interactive(scheme *sc); static INLINE int is_one_of(char *s, int c); static int alloc_cellseg(scheme *sc, int n); static long binary_decode(const char *s); static INLINE pointer get_cell(scheme *sc, pointer a, pointer b); static pointer _get_cell(scheme *sc, pointer a, pointer b); static pointer reserve_cells(scheme *sc, int n); static pointer get_consecutive_cells(scheme *sc, int n); static pointer find_consecutive_cells(scheme *sc, int n); static int finalize_cell(scheme *sc, pointer a); static int count_consecutive_cells(pointer x, int needed); static pointer find_slot_in_env(scheme *sc, pointer env, pointer sym, int all); static pointer mk_number(scheme *sc, num n); static char *store_string(scheme *sc, int len, const char *str, char fill); static pointer mk_vector(scheme *sc, int len); static pointer mk_atom(scheme *sc, char *q); static pointer mk_sharp_const(scheme *sc, char *name); static pointer mk_port(scheme *sc, port *p); static pointer port_from_filename(scheme *sc, const char *fn, int prop); static pointer port_from_file(scheme *sc, FILE *, int prop); static pointer port_from_string(scheme *sc, char *start, char *past_the_end, int prop); static port *port_rep_from_filename(scheme *sc, const char *fn, int prop); static port *port_rep_from_file(scheme *sc, FILE *, int prop); static port *port_rep_from_string(scheme *sc, char *start, char *past_the_end, int prop); static void port_close(scheme *sc, pointer p, int flag); static void mark(pointer a); static void gc(scheme *sc, pointer a, pointer b); static int basic_inchar(port *pt); static int inchar(scheme *sc); static void backchar(scheme *sc, int c); static char *readstr_upto(scheme *sc, char *delim); static pointer readstrexp(scheme *sc); static INLINE int skipspace(scheme *sc); static int token(scheme *sc); static void printslashstring(scheme *sc, char *s, int len); static void atom2str(scheme *sc, pointer l, int f, char **pp, int *plen); static void printatom(scheme *sc, pointer l, int f); static pointer mk_proc(scheme *sc, enum scheme_opcodes op); static pointer mk_closure(scheme *sc, pointer c, pointer e); static pointer mk_continuation(scheme *sc, pointer d); static pointer reverse(scheme *sc, pointer term, pointer list); static pointer reverse_in_place(scheme *sc, pointer term, pointer list); static pointer revappend(scheme *sc, pointer a, pointer b); static void dump_stack_preallocate_frame(scheme *sc); static void dump_stack_mark(scheme *); struct op_code_info { char name[31]; /* strlen ("call-with-current-continuation") + 1 */ unsigned char min_arity; unsigned char max_arity; char arg_tests_encoding[3]; }; static const struct op_code_info dispatch_table[]; static int check_arguments (scheme *sc, const struct op_code_info *pcd, char *msg, size_t msg_size); static void Eval_Cycle(scheme *sc, enum scheme_opcodes op); static void assign_syntax(scheme *sc, enum scheme_opcodes op, char *name); static int syntaxnum(scheme *sc, pointer p); static void assign_proc(scheme *sc, enum scheme_opcodes, const char *name); #define num_ivalue(n) (n.is_fixnum?(n).value.ivalue:(long)(n).value.rvalue) #define num_rvalue(n) (!n.is_fixnum?(n).value.rvalue:(double)(n).value.ivalue) static num num_add(num a, num b) { num ret; ret.is_fixnum=a.is_fixnum && b.is_fixnum; if(ret.is_fixnum) { ret.value.ivalue= a.value.ivalue+b.value.ivalue; } else { ret.value.rvalue=num_rvalue(a)+num_rvalue(b); } return ret; } static num num_mul(num a, num b) { num ret; ret.is_fixnum=a.is_fixnum && b.is_fixnum; if(ret.is_fixnum) { ret.value.ivalue= a.value.ivalue*b.value.ivalue; } else { ret.value.rvalue=num_rvalue(a)*num_rvalue(b); } return ret; } static num num_div(num a, num b) { num ret; ret.is_fixnum=a.is_fixnum && b.is_fixnum && a.value.ivalue%b.value.ivalue==0; if(ret.is_fixnum) { ret.value.ivalue= a.value.ivalue/b.value.ivalue; } else { ret.value.rvalue=num_rvalue(a)/num_rvalue(b); } return ret; } static num num_intdiv(num a, num b) { num ret; ret.is_fixnum=a.is_fixnum && b.is_fixnum; if(ret.is_fixnum) { ret.value.ivalue= a.value.ivalue/b.value.ivalue; } else { ret.value.rvalue=num_rvalue(a)/num_rvalue(b); } return ret; } static num num_sub(num a, num b) { num ret; ret.is_fixnum=a.is_fixnum && b.is_fixnum; if(ret.is_fixnum) { ret.value.ivalue= a.value.ivalue-b.value.ivalue; } else { ret.value.rvalue=num_rvalue(a)-num_rvalue(b); } return ret; } static num num_rem(num a, num b) { num ret; long e1, e2, res; ret.is_fixnum=a.is_fixnum && b.is_fixnum; e1=num_ivalue(a); e2=num_ivalue(b); res=e1%e2; /* remainder should have same sign as second operand */ if (res > 0) { if (e1 < 0) { res -= labs(e2); } } else if (res < 0) { if (e1 > 0) { res += labs(e2); } } ret.value.ivalue=res; return ret; } static num num_mod(num a, num b) { num ret; long e1, e2, res; ret.is_fixnum=a.is_fixnum && b.is_fixnum; e1=num_ivalue(a); e2=num_ivalue(b); res=e1%e2; /* modulo should have same sign as second operand */ if (res * e2 < 0) { res += e2; } ret.value.ivalue=res; return ret; } static int num_eq(num a, num b) { int ret; int is_fixnum=a.is_fixnum && b.is_fixnum; if(is_fixnum) { ret= a.value.ivalue==b.value.ivalue; } else { ret=num_rvalue(a)==num_rvalue(b); } return ret; } static int num_gt(num a, num b) { int ret; int is_fixnum=a.is_fixnum && b.is_fixnum; if(is_fixnum) { ret= a.value.ivalue>b.value.ivalue; } else { ret=num_rvalue(a)>num_rvalue(b); } return ret; } static int num_ge(num a, num b) { return !num_lt(a,b); } static int num_lt(num a, num b) { int ret; int is_fixnum=a.is_fixnum && b.is_fixnum; if(is_fixnum) { ret= a.value.ivaluedce) { return ce; } else if(dfl-DBL_MIN; } static long binary_decode(const char *s) { long x=0; while(*s!=0 && (*s=='1' || *s=='0')) { x<<=1; x+=*s-'0'; s++; } return x; } /* * Copying values. * * Occasionally, we need to copy a value from one location in the * storage to another. Scheme objects are fine. Some primitive * objects, however, require finalization, usually to free resources. * * For these values, we either make a copy or acquire a reference. */ /* * Copy SRC to DST. * * Copies the representation of SRC to DST. This makes SRC * indistinguishable from DST from the perspective of a Scheme * expression modulo the fact that they reside at a different location * in the store. * * Conditions: * * - SRC must not be a vector. * - Caller must ensure that any resources associated with the * value currently stored in DST is accounted for. */ static void copy_value(scheme *sc, pointer dst, pointer src) { memcpy(dst, src, sizeof *src); /* We may need to make a copy or acquire a reference. */ if (typeflag(dst) & T_FINALIZE) switch (type(dst)) { case T_STRING: strvalue(dst) = store_string(sc, strlength(dst), strvalue(dst), 0); break; case T_PORT: /* XXX acquire reference */ assert (!"implemented"); break; case T_FOREIGN_OBJECT: /* XXX acquire reference */ assert (!"implemented"); break; case T_VECTOR: assert (!"vectors cannot be copied"); } } /* Tags are like property lists, but can be attached to arbitrary * values. */ static pointer mk_tagged_value(scheme *sc, pointer v, pointer tag_car, pointer tag_cdr) { pointer r, t; assert(! is_vector(v)); r = get_consecutive_cells(sc, 2); if (r == sc->sink) return sc->sink; copy_value(sc, r, v); typeflag(r) |= T_TAGGED; t = r + 1; typeflag(t) = T_PAIR; car(t) = tag_car; cdr(t) = tag_cdr; return r; } static INLINE int has_tag(pointer v) { return !! (typeflag(v) & T_TAGGED); } static INLINE pointer get_tag(scheme *sc, pointer v) { if (has_tag(v)) return v + 1; return sc->NIL; } /* Low-level allocator. * * Memory is allocated in segments. Every segment holds a fixed * number of cells. Segments are linked into a list, sorted in * reverse address order (i.e. those with a higher address first). * This is used in the garbage collector to build the freelist in * address order. */ struct cell_segment { struct cell_segment *next; void *alloc; pointer cells; size_t cells_len; }; /* Allocate a new cell segment but do not make it available yet. */ static int _alloc_cellseg(scheme *sc, size_t len, struct cell_segment **segment) { int adj = ADJ; void *cp; if (adj < sizeof(struct cell)) adj = sizeof(struct cell); /* The segment header is conveniently allocated with the cells. */ cp = sc->malloc(sizeof **segment + len * sizeof(struct cell) + adj); if (cp == NULL) return 1; *segment = cp; (*segment)->next = NULL; (*segment)->alloc = cp; cp = (void *) ((uintptr_t) cp + sizeof **segment); /* adjust in TYPE_BITS-bit boundary */ if (((uintptr_t) cp) % adj != 0) cp = (void *) (adj * ((uintptr_t) cp / adj + 1)); (*segment)->cells = cp; (*segment)->cells_len = len; return 0; } /* Deallocate a cell segment. Returns the next cell segment. * Convenient for deallocation in a loop. */ static struct cell_segment * _dealloc_cellseg(scheme *sc, struct cell_segment *segment) { struct cell_segment *next; if (segment == NULL) return NULL; next = segment->next; sc->free(segment->alloc); return next; } /* allocate new cell segment */ static int alloc_cellseg(scheme *sc, int n) { pointer last; pointer p; int k; for (k = 0; k < n; k++) { struct cell_segment *new, **s; if (_alloc_cellseg(sc, CELL_SEGSIZE, &new)) { return k; } /* insert new segment in reverse address order */ for (s = &sc->cell_segments; *s && (uintptr_t) (*s)->alloc > (uintptr_t) new->alloc; s = &(*s)->next) { /* walk */ } new->next = *s; *s = new; sc->fcells += new->cells_len; last = new->cells + new->cells_len - 1; for (p = new->cells; p <= last; p++) { typeflag(p) = 0; cdr(p) = p + 1; car(p) = sc->NIL; } /* insert new cells in address order on free list */ if (sc->free_cell == sc->NIL || p < sc->free_cell) { cdr(last) = sc->free_cell; sc->free_cell = new->cells; } else { p = sc->free_cell; while (cdr(p) != sc->NIL && (uintptr_t) new->cells > (uintptr_t) cdr(p)) p = cdr(p); cdr(last) = cdr(p); cdr(p) = new->cells; } } return n; } /* Controlling the garbage collector. * * Every time a cell is allocated, the interpreter may run out of free * cells and do a garbage collection. This is problematic because it * might garbage collect objects that have been allocated, but are not * yet made available to the interpreter. * * Previously, we would plug such newly allocated cells into the list * of newly allocated objects rooted at car(sc->sink), but that * requires allocating yet another cell increasing pressure on the * memory management system. * * A faster alternative is to preallocate the cells needed for an * operation and make sure the garbage collection is not run until all * allocated objects are plugged in. This can be done with gc_disable * and gc_enable. */ /* The garbage collector is enabled if the inhibit counter is * zero. */ #define GC_ENABLED 0 /* For now we provide a way to disable this optimization for * benchmarking and because it produces slightly smaller code. */ #ifndef USE_GC_LOCKING # define USE_GC_LOCKING 1 #endif /* To facilitate nested calls to gc_disable, functions that allocate * more than one cell may define a macro, e.g. foo_allocates. This * macro can be used to compute the amount of preallocation at the * call site with the help of this macro. */ #define gc_reservations(fn) fn ## _allocates #if USE_GC_LOCKING /* Report a shortage in reserved cells, and terminate the program. */ static void gc_reservation_failure(struct scheme *sc) { #ifdef NDEBUG fprintf(stderr, "insufficient reservation\n"); #else fprintf(stderr, "insufficient %s reservation in line %d\n", sc->frame_freelist == sc->NIL ? "frame" : "cell", sc->reserved_lineno); #endif abort(); } /* Disable the garbage collection and reserve the given number of * cells. gc_disable may be nested, but the enclosing reservation * must include the reservations of all nested calls. Note: You must * re-enable the gc before calling Error_X. */ static void _gc_disable(struct scheme *sc, size_t reserve, int lineno) { if (sc->inhibit_gc == 0) { reserve_cells(sc, (reserve)); sc->reserved_cells = (reserve); #ifdef NDEBUG (void) lineno; #else sc->reserved_lineno = lineno; #endif } else if (sc->reserved_cells < (reserve)) gc_reservation_failure (sc); sc->inhibit_gc += 1; } #define gc_disable(sc, reserve) \ do { \ if (sc->frame_freelist == sc->NIL) { \ if (gc_enabled(sc)) \ dump_stack_preallocate_frame(sc); \ else \ gc_reservation_failure(sc); \ } \ _gc_disable (sc, reserve, __LINE__); \ } while (0) /* Enable the garbage collector. */ #define gc_enable(sc) \ do { \ assert(sc->inhibit_gc); \ sc->inhibit_gc -= 1; \ } while (0) /* Test whether the garbage collector is enabled. */ #define gc_enabled(sc) \ (sc->inhibit_gc == GC_ENABLED) /* Consume a reserved cell. */ #define gc_consume(sc) \ do { \ assert(! gc_enabled (sc)); \ if (sc->reserved_cells == 0) \ gc_reservation_failure (sc); \ sc->reserved_cells -= 1; \ } while (0) #else /* USE_GC_LOCKING */ #define gc_reservation_failure(sc) (void) 0 #define gc_disable(sc, reserve) \ do { \ if (sc->frame_freelist == sc->NIL) \ dump_stack_preallocate_frame(sc); \ } while (0) #define gc_enable(sc) (void) 0 #define gc_enabled(sc) 1 #define gc_consume(sc) (void) 0 #endif /* USE_GC_LOCKING */ static INLINE pointer get_cell_x(scheme *sc, pointer a, pointer b) { if (! gc_enabled (sc) || sc->free_cell != sc->NIL) { pointer x = sc->free_cell; if (! gc_enabled (sc)) gc_consume (sc); sc->free_cell = cdr(x); --sc->fcells; return (x); } assert (gc_enabled (sc)); return _get_cell (sc, a, b); } /* get new cell. parameter a, b is marked by gc. */ static pointer _get_cell(scheme *sc, pointer a, pointer b) { pointer x; if(sc->no_memory) { return sc->sink; } assert (gc_enabled (sc)); if (sc->free_cell == sc->NIL) { gc(sc,a, b); if (sc->free_cell == sc->NIL) { sc->no_memory=1; return sc->sink; } } x = sc->free_cell; sc->free_cell = cdr(x); --sc->fcells; return (x); } /* make sure that there is a given number of cells free */ static pointer reserve_cells(scheme *sc, int n) { if(sc->no_memory) { return sc->NIL; } /* Are there enough cells available? */ if (sc->fcells < n) { /* If not, try gc'ing some */ gc(sc, sc->NIL, sc->NIL); if (sc->fcells < n) { /* If there still aren't, try getting more heap */ if (!alloc_cellseg(sc,1)) { sc->no_memory=1; return sc->NIL; } } if (sc->fcells < n) { /* If all fail, report failure */ sc->no_memory=1; return sc->NIL; } } return (sc->T); } static pointer get_consecutive_cells(scheme *sc, int n) { pointer x; if(sc->no_memory) { return sc->sink; } /* Are there any cells available? */ x=find_consecutive_cells(sc,n); if (x != sc->NIL) { return x; } /* If not, try gc'ing some */ gc(sc, sc->NIL, sc->NIL); x=find_consecutive_cells(sc,n); if (x != sc->NIL) { return x; } /* If there still aren't, try getting more heap */ if (!alloc_cellseg(sc,1)) { sc->no_memory=1; return sc->sink; } x=find_consecutive_cells(sc,n); if (x != sc->NIL) { return x; } /* If all fail, report failure */ sc->no_memory=1; return sc->sink; } static int count_consecutive_cells(pointer x, int needed) { int n=1; while(cdr(x)==x+1) { x=cdr(x); n++; if(n>needed) return n; } return n; } static pointer find_consecutive_cells(scheme *sc, int n) { pointer *pp; int cnt; pp=&sc->free_cell; while(*pp!=sc->NIL) { cnt=count_consecutive_cells(*pp,n); if(cnt>=n) { pointer x=*pp; *pp=cdr(*pp+n-1); sc->fcells -= n; return x; } pp=&cdr(*pp+cnt-1); } return sc->NIL; } /* Free a cell. This is dangerous. Only free cells that are not * referenced. */ static INLINE void free_cell(scheme *sc, pointer a) { cdr(a) = sc->free_cell; sc->free_cell = a; sc->fcells += 1; } /* Free a cell and retrieve its content. This is dangerous. Only * free cells that are not referenced. */ static INLINE void free_cons(scheme *sc, pointer a, pointer *r_car, pointer *r_cdr) { *r_car = car(a); *r_cdr = cdr(a); free_cell(sc, a); } /* To retain recent allocs before interpreter knows about them - Tehom */ static void push_recent_alloc(scheme *sc, pointer recent, pointer extra) { pointer holder = get_cell_x(sc, recent, extra); typeflag(holder) = T_PAIR | T_IMMUTABLE; car(holder) = recent; cdr(holder) = car(sc->sink); car(sc->sink) = holder; } static INLINE void ok_to_freely_gc(scheme *sc) { pointer a = car(sc->sink), next; car(sc->sink) = sc->NIL; while (a != sc->NIL) { next = cdr(a); free_cell(sc, a); a = next; } } static pointer get_cell(scheme *sc, pointer a, pointer b) { pointer cell = get_cell_x(sc, a, b); /* For right now, include "a" and "b" in "cell" so that gc doesn't think they are garbage. */ /* Tentatively record it as a pair so gc understands it. */ typeflag(cell) = T_PAIR; car(cell) = a; cdr(cell) = b; if (gc_enabled (sc)) push_recent_alloc(sc, cell, sc->NIL); return cell; } static pointer get_vector_object(scheme *sc, int len, pointer init) { pointer cells = get_consecutive_cells(sc, vector_size(len)); int i; int alloc_len = 1 + 3 * (vector_size(len) - 1); if(sc->no_memory) { return sc->sink; } /* Record it as a vector so that gc understands it. */ typeflag(cells) = (T_VECTOR | T_ATOM | T_FINALIZE); vector_length(cells) = len; fill_vector(cells,init); /* Initialize the unused slots at the end. */ assert (alloc_len - len < 3); for (i = len; i < alloc_len; i++) cells->_object._vector._elements[i] = sc->NIL; if (gc_enabled (sc)) push_recent_alloc(sc, cells, sc->NIL); return cells; } /* Medium level cell allocation */ /* get new cons cell */ pointer _cons(scheme *sc, pointer a, pointer b, int immutable) { pointer x = get_cell(sc,a, b); typeflag(x) = T_PAIR; if(immutable) { setimmutable(x); } car(x) = a; cdr(x) = b; return (x); } /* ========== oblist implementation ========== */ #ifndef USE_OBJECT_LIST static int hash_fn(const char *key, int table_size); static pointer oblist_initial_value(scheme *sc) { /* There are about 768 symbols used after loading the * interpreter. */ return mk_vector(sc, 1009); } /* Lookup the symbol NAME. Returns the symbol, or NIL if it does not * exist. In that case, SLOT points to the point where the new symbol * is to be inserted. */ static INLINE pointer oblist_find_by_name(scheme *sc, const char *name, pointer **slot) { int location; pointer x; char *s; int d; location = hash_fn(name, vector_length(sc->oblist)); for (*slot = vector_elem_slot(sc->oblist, location), x = **slot; x != sc->NIL; *slot = &cdr(x), x = **slot) { s = symname(car(x)); /* case-insensitive, per R5RS section 2. */ d = stricmp(name, s); if (d == 0) return car(x); /* Hit. */ else if (d > 0) break; /* Miss. */ } return sc->NIL; } static pointer oblist_all_symbols(scheme *sc) { int i; pointer x; pointer ob_list = sc->NIL; for (i = 0; i < vector_length(sc->oblist); i++) { for (x = vector_elem(sc->oblist, i); x != sc->NIL; x = cdr(x)) { ob_list = cons(sc, x, ob_list); } } return ob_list; } #else static pointer oblist_initial_value(scheme *sc) { return sc->NIL; } /* Lookup the symbol NAME. Returns the symbol, or NIL if it does not * exist. In that case, SLOT points to the point where the new symbol * is to be inserted. */ static INLINE pointer oblist_find_by_name(scheme *sc, const char *name, pointer **slot) { pointer x; char *s; int d; for (*slot = &sc->oblist, x = **slot; x != sc->NIL; *slot = &cdr(x), x = **slot) { s = symname(car(x)); /* case-insensitive, per R5RS section 2. */ d = stricmp(name, s); if (d == 0) return car(x); /* Hit. */ else if (d > 0) break; /* Miss. */ } return sc->NIL; } static pointer oblist_all_symbols(scheme *sc) { return sc->oblist; } #endif /* Add a new symbol NAME at SLOT. SLOT must be obtained using * oblist_find_by_name, and no insertion must be done between * obtaining the SLOT and calling this function. Returns the new * symbol. */ static pointer oblist_add_by_name(scheme *sc, const char *name, pointer *slot) { #define oblist_add_by_name_allocates 3 pointer x; gc_disable(sc, gc_reservations (oblist_add_by_name)); x = immutable_cons(sc, mk_string(sc, name), sc->NIL); typeflag(x) = T_SYMBOL; setimmutable(car(x)); *slot = immutable_cons(sc, x, *slot); gc_enable(sc); return x; } static pointer mk_port(scheme *sc, port *p) { pointer x = get_cell(sc, sc->NIL, sc->NIL); typeflag(x) = T_PORT|T_ATOM|T_FINALIZE; x->_object._port=p; return (x); } pointer mk_foreign_func(scheme *sc, foreign_func f) { pointer x = get_cell(sc, sc->NIL, sc->NIL); typeflag(x) = (T_FOREIGN | T_ATOM); x->_object._ff=f; return (x); } pointer mk_foreign_object(scheme *sc, const foreign_object_vtable *vtable, void *data) { pointer x = get_cell(sc, sc->NIL, sc->NIL); typeflag(x) = (T_FOREIGN_OBJECT | T_ATOM | T_FINALIZE); x->_object._foreign_object._vtable=vtable; x->_object._foreign_object._data = data; return (x); } INTERFACE pointer mk_character(scheme *sc, int c) { pointer x = get_cell(sc,sc->NIL, sc->NIL); typeflag(x) = (T_CHARACTER | T_ATOM); ivalue_unchecked(x)= c; set_num_integer(x); return (x); } #if USE_SMALL_INTEGERS static const struct cell small_integers[] = { #define DEFINE_INTEGER(n) { T_NUMBER | T_ATOM | MARK, {{ 1, {n}}}}, #include "small-integers.h" #undef DEFINE_INTEGER {0} }; #define MAX_SMALL_INTEGER (sizeof small_integers / sizeof *small_integers - 1) static INLINE pointer mk_small_integer(scheme *sc, long n) { #define mk_small_integer_allocates 0 (void) sc; assert(0 <= n && n < MAX_SMALL_INTEGER); return (pointer) &small_integers[n]; } #else #define mk_small_integer_allocates 1 #define mk_small_integer mk_integer #endif /* get number atom (integer) */ INTERFACE pointer mk_integer(scheme *sc, long n) { pointer x; #if USE_SMALL_INTEGERS if (0 <= n && n < MAX_SMALL_INTEGER) return mk_small_integer(sc, n); #endif x = get_cell(sc,sc->NIL, sc->NIL); typeflag(x) = (T_NUMBER | T_ATOM); ivalue_unchecked(x)= n; set_num_integer(x); return (x); } INTERFACE pointer mk_real(scheme *sc, double n) { pointer x = get_cell(sc,sc->NIL, sc->NIL); typeflag(x) = (T_NUMBER | T_ATOM); rvalue_unchecked(x)= n; set_num_real(x); return (x); } static pointer mk_number(scheme *sc, num n) { if(n.is_fixnum) { return mk_integer(sc,n.value.ivalue); } else { return mk_real(sc,n.value.rvalue); } } /* allocate name to string area */ static char *store_string(scheme *sc, int len_str, const char *str, char fill) { char *q; q=(char*)sc->malloc(len_str+1); if(q==0) { sc->no_memory=1; return sc->strbuff; } if(str!=0) { memcpy (q, str, len_str); q[len_str]=0; } else { memset(q, fill, len_str); q[len_str]=0; } return (q); } /* get new string */ INTERFACE pointer mk_string(scheme *sc, const char *str) { return mk_counted_string(sc,str,strlen(str)); } INTERFACE pointer mk_counted_string(scheme *sc, const char *str, int len) { pointer x = get_cell(sc, sc->NIL, sc->NIL); typeflag(x) = (T_STRING | T_ATOM | T_FINALIZE); strvalue(x) = store_string(sc,len,str,0); strlength(x) = len; return (x); } INTERFACE pointer mk_empty_string(scheme *sc, int len, char fill) { pointer x = get_cell(sc, sc->NIL, sc->NIL); typeflag(x) = (T_STRING | T_ATOM | T_FINALIZE); strvalue(x) = store_string(sc,len,0,fill); strlength(x) = len; return (x); } INTERFACE static pointer mk_vector(scheme *sc, int len) { return get_vector_object(sc,len,sc->NIL); } INTERFACE static void fill_vector(pointer vec, pointer obj) { size_t i; assert (is_vector (vec)); for(i = 0; i < vector_length(vec); i++) { vec->_object._vector._elements[i] = obj; } } INTERFACE static pointer *vector_elem_slot(pointer vec, int ielem) { assert (is_vector (vec)); assert (ielem < vector_length(vec)); return &vec->_object._vector._elements[ielem]; } INTERFACE static pointer vector_elem(pointer vec, int ielem) { assert (is_vector (vec)); assert (ielem < vector_length(vec)); return vec->_object._vector._elements[ielem]; } INTERFACE static pointer set_vector_elem(pointer vec, int ielem, pointer a) { assert (is_vector (vec)); assert (ielem < vector_length(vec)); vec->_object._vector._elements[ielem] = a; return a; } /* get new symbol */ INTERFACE pointer mk_symbol(scheme *sc, const char *name) { #define mk_symbol_allocates oblist_add_by_name_allocates pointer x; pointer *slot; /* first check oblist */ x = oblist_find_by_name(sc, name, &slot); if (x != sc->NIL) { return (x); } else { x = oblist_add_by_name(sc, name, slot); return (x); } } INTERFACE pointer gensym(scheme *sc) { pointer x; pointer *slot; char name[40]; for(; sc->gensym_cntgensym_cnt++) { snprintf(name,40,"gensym-%ld",sc->gensym_cnt); /* first check oblist */ x = oblist_find_by_name(sc, name, &slot); if (x != sc->NIL) { continue; } else { x = oblist_add_by_name(sc, name, slot); return (x); } } return sc->NIL; } /* double the size of the string buffer */ static int expand_strbuff(scheme *sc) { size_t new_size = sc->strbuff_size * 2; char *new_buffer = sc->malloc(new_size); if (new_buffer == 0) { sc->no_memory = 1; return 1; } memcpy(new_buffer, sc->strbuff, sc->strbuff_size); sc->free(sc->strbuff); sc->strbuff = new_buffer; sc->strbuff_size = new_size; return 0; } /* make symbol or number atom from string */ static pointer mk_atom(scheme *sc, char *q) { char c, *p; int has_dec_point=0; int has_fp_exp = 0; #if USE_COLON_HOOK char *next; next = p = q; while ((next = strstr(next, "::")) != 0) { /* Keep looking for the last occurrence. */ p = next; next = next + 2; } if (p != q) { *p=0; return cons(sc, sc->COLON_HOOK, cons(sc, cons(sc, sc->QUOTE, cons(sc, mk_symbol(sc, strlwr(p + 2)), sc->NIL)), cons(sc, mk_atom(sc, q), sc->NIL))); } #endif p = q; c = *p++; if ((c == '+') || (c == '-')) { c = *p++; if (c == '.') { has_dec_point=1; c = *p++; } if (!isdigit(c)) { return (mk_symbol(sc, strlwr(q))); } } else if (c == '.') { has_dec_point=1; c = *p++; if (!isdigit(c)) { return (mk_symbol(sc, strlwr(q))); } } else if (!isdigit(c)) { return (mk_symbol(sc, strlwr(q))); } for ( ; (c = *p) != 0; ++p) { if (!isdigit(c)) { if(c=='.') { if(!has_dec_point) { has_dec_point=1; continue; } } else if ((c == 'e') || (c == 'E')) { if(!has_fp_exp) { has_dec_point = 1; /* decimal point illegal from now on */ p++; if ((*p == '-') || (*p == '+') || isdigit(*p)) { continue; } } } return (mk_symbol(sc, strlwr(q))); } } if(has_dec_point) { return mk_real(sc,atof(q)); } return (mk_integer(sc, atol(q))); } /* make constant */ static pointer mk_sharp_const(scheme *sc, char *name) { long x; char tmp[STRBUFFSIZE]; if (!strcmp(name, "t")) return (sc->T); else if (!strcmp(name, "f")) return (sc->F); else if (*name == 'o') {/* #o (octal) */ snprintf(tmp, STRBUFFSIZE, "0%s", name+1); sscanf(tmp, "%lo", (long unsigned *)&x); return (mk_integer(sc, x)); } else if (*name == 'd') { /* #d (decimal) */ sscanf(name+1, "%ld", (long int *)&x); return (mk_integer(sc, x)); } else if (*name == 'x') { /* #x (hex) */ snprintf(tmp, STRBUFFSIZE, "0x%s", name+1); sscanf(tmp, "%lx", (long unsigned *)&x); return (mk_integer(sc, x)); } else if (*name == 'b') { /* #b (binary) */ x = binary_decode(name+1); return (mk_integer(sc, x)); } else if (*name == '\\') { /* #\w (character) */ int c=0; if(stricmp(name+1,"space")==0) { c=' '; } else if(stricmp(name+1,"newline")==0) { c='\n'; } else if(stricmp(name+1,"return")==0) { c='\r'; } else if(stricmp(name+1,"tab")==0) { c='\t'; } else if(name[1]=='x' && name[2]!=0) { int c1=0; if(sscanf(name+2,"%x",(unsigned int *)&c1)==1 && c1 < UCHAR_MAX) { c=c1; } else { return sc->NIL; } #if USE_ASCII_NAMES } else if(is_ascii_name(name+1,&c)) { /* nothing */ #endif } else if(name[2]==0) { c=name[1]; } else { return sc->NIL; } return mk_character(sc,c); } else return (sc->NIL); } /* ========== garbage collector ========== */ const int frame_length; static void dump_stack_deallocate_frame(scheme *sc, pointer frame); /*-- * We use algorithm E (Knuth, The Art of Computer Programming Vol.1, * sec. 2.3.5), the Schorr-Deutsch-Waite link-inversion algorithm, * for marking. */ static void mark(pointer a) { pointer t, q, p; t = (pointer) 0; p = a; E2: if (! is_mark(p)) setmark(p); if (is_vector(p) || is_frame(p)) { int i; int len = is_vector(p) ? vector_length(p) : frame_length; for (i = 0; i < len; i++) { mark(p->_object._vector._elements[i]); } } #if SHOW_ERROR_LINE else if (is_port(p)) { port *pt = p->_object._port; mark(pt->curr_line); mark(pt->filename); } #endif /* Mark tag if p has one. */ if (has_tag(p)) mark(p + 1); if (is_atom(p)) goto E6; /* E4: down car */ q = car(p); if (q && !is_mark(q)) { setatom(p); /* a note that we have moved car */ car(p) = t; t = p; p = q; goto E2; } E5: q = cdr(p); /* down cdr */ if (q && !is_mark(q)) { cdr(p) = t; t = p; p = q; goto E2; } E6: /* up. Undo the link switching from steps E4 and E5. */ if (!t) return; q = t; if (is_atom(q)) { clratom(q); t = car(q); car(q) = p; p = q; goto E5; } else { t = cdr(q); cdr(q) = p; p = q; goto E6; } } /* garbage collection. parameter a, b is marked. */ static void gc(scheme *sc, pointer a, pointer b) { pointer p; struct cell_segment *s; int i; assert (gc_enabled (sc)); if(sc->gc_verbose) { putstr(sc, "gc..."); } /* mark system globals */ mark(sc->oblist); mark(sc->global_env); /* mark current registers */ mark(sc->args); mark(sc->envir); mark(sc->code); history_mark(sc); dump_stack_mark(sc); mark(sc->value); mark(sc->inport); mark(sc->save_inport); mark(sc->outport); mark(sc->loadport); for (i = 0; i <= sc->file_i; i++) { mark(sc->load_stack[i].filename); mark(sc->load_stack[i].curr_line); } /* Mark recent objects the interpreter doesn't know about yet. */ mark(car(sc->sink)); /* Mark any older stuff above nested C calls */ mark(sc->c_nest); /* mark variables a, b */ mark(a); mark(b); /* garbage collect */ clrmark(sc->NIL); sc->fcells = 0; sc->free_cell = sc->NIL; /* free-list is kept sorted by address so as to maintain consecutive ranges, if possible, for use with vectors. Here we scan the cells (which are also kept sorted by address) downwards to build the free-list in sorted order. */ for (s = sc->cell_segments; s; s = s->next) { p = s->cells + s->cells_len; while (--p >= s->cells) { if ((typeflag(p) & 1) == 0) /* All types have the LSB set. This is not a typeflag. */ continue; if (is_mark(p)) { clrmark(p); } else { /* reclaim cell */ if ((typeflag(p) & T_FINALIZE) == 0 || finalize_cell(sc, p)) { /* Reclaim cell. */ ++sc->fcells; typeflag(p) = 0; car(p) = sc->NIL; cdr(p) = sc->free_cell; sc->free_cell = p; } } } } if (sc->gc_verbose) { char msg[80]; snprintf(msg,80,"done: %ld cells were recovered.\n", sc->fcells); putstr(sc,msg); } /* if only a few recovered, get more to avoid fruitless gc's */ if (sc->fcells < CELL_MINRECOVER && alloc_cellseg(sc, 1) == 0) sc->no_memory = 1; } /* Finalize A. Returns true if a can be added to the list of free * cells. */ static int finalize_cell(scheme *sc, pointer a) { switch (type(a)) { case T_STRING: sc->free(strvalue(a)); break; case T_PORT: if(a->_object._port->kind&port_file && a->_object._port->rep.stdio.closeit) { port_close(sc,a,port_input|port_output); } else if (a->_object._port->kind & port_srfi6) { sc->free(a->_object._port->rep.string.start); } sc->free(a->_object._port); break; case T_FOREIGN_OBJECT: a->_object._foreign_object._vtable->finalize(sc, a->_object._foreign_object._data); break; case T_VECTOR: do { int i; for (i = vector_size(vector_length(a)) - 1; i > 0; i--) { pointer p = a + i; typeflag(p) = 0; car(p) = sc->NIL; cdr(p) = sc->free_cell; sc->free_cell = p; sc->fcells += 1; } } while (0); break; case T_FRAME: dump_stack_deallocate_frame(sc, a); return 0; /* Do not free cell. */ } return 1; /* Free cell. */ } #if SHOW_ERROR_LINE static void port_clear_location (scheme *sc, port *p) { p->curr_line = sc->NIL; p->filename = sc->NIL; } static void port_increment_current_line (scheme *sc, port *p, long delta) { if (delta == 0) return; p->curr_line = mk_integer(sc, ivalue_unchecked(p->curr_line) + delta); } static void port_init_location (scheme *sc, port *p, pointer name) { p->curr_line = mk_integer(sc, 0); p->filename = name ? name : mk_string(sc, ""); } #else static void port_clear_location (scheme *sc, port *p) { } static void port_increment_current_line (scheme *sc, port *p, long delta) { } static void port_init_location (scheme *sc, port *p, pointer name) { } #endif /* ========== Routines for Reading ========== */ static int file_push(scheme *sc, pointer fname) { FILE *fin = NULL; if (sc->file_i == MAXFIL-1) return 0; fin = fopen(string_value(fname), "r"); if(fin!=0) { sc->file_i++; sc->load_stack[sc->file_i].kind=port_file|port_input; sc->load_stack[sc->file_i].rep.stdio.file=fin; sc->load_stack[sc->file_i].rep.stdio.closeit=1; sc->nesting_stack[sc->file_i]=0; sc->loadport->_object._port=sc->load_stack+sc->file_i; port_init_location(sc, &sc->load_stack[sc->file_i], fname); } return fin!=0; } static void file_pop(scheme *sc) { if(sc->file_i != 0) { sc->nesting=sc->nesting_stack[sc->file_i]; port_close(sc,sc->loadport,port_input); port_clear_location(sc, &sc->load_stack[sc->file_i]); sc->file_i--; sc->loadport->_object._port=sc->load_stack+sc->file_i; } } static int file_interactive(scheme *sc) { return sc->file_i==0 && sc->load_stack[0].rep.stdio.file==stdin && sc->inport->_object._port->kind&port_file; } static port *port_rep_from_filename(scheme *sc, const char *fn, int prop) { FILE *f; char *rw; port *pt; if(prop==(port_input|port_output)) { rw="a+"; } else if(prop==port_output) { rw="w"; } else { rw="r"; } f=fopen(fn,rw); if(f==0) { return 0; } pt=port_rep_from_file(sc,f,prop); pt->rep.stdio.closeit=1; port_init_location(sc, pt, mk_string(sc, fn)); return pt; } static pointer port_from_filename(scheme *sc, const char *fn, int prop) { port *pt; pt=port_rep_from_filename(sc,fn,prop); if(pt==0) { return sc->NIL; } return mk_port(sc,pt); } static port *port_rep_from_file(scheme *sc, FILE *f, int prop) { port *pt; pt = (port *)sc->malloc(sizeof *pt); if (pt == NULL) { return NULL; } pt->kind = port_file | prop; pt->rep.stdio.file = f; pt->rep.stdio.closeit = 0; port_init_location(sc, pt, NULL); return pt; } static pointer port_from_file(scheme *sc, FILE *f, int prop) { port *pt; pt=port_rep_from_file(sc,f,prop); if(pt==0) { return sc->NIL; } return mk_port(sc,pt); } static port *port_rep_from_string(scheme *sc, char *start, char *past_the_end, int prop) { port *pt; pt=(port*)sc->malloc(sizeof(port)); if(pt==0) { return 0; } pt->kind=port_string|prop; pt->rep.string.start=start; pt->rep.string.curr=start; pt->rep.string.past_the_end=past_the_end; port_init_location(sc, pt, NULL); return pt; } static pointer port_from_string(scheme *sc, char *start, char *past_the_end, int prop) { port *pt; pt=port_rep_from_string(sc,start,past_the_end,prop); if(pt==0) { return sc->NIL; } return mk_port(sc,pt); } #define BLOCK_SIZE 256 static port *port_rep_from_scratch(scheme *sc) { port *pt; char *start; pt=(port*)sc->malloc(sizeof(port)); if(pt==0) { return 0; } start=sc->malloc(BLOCK_SIZE); if(start==0) { return 0; } memset(start,' ',BLOCK_SIZE-1); start[BLOCK_SIZE-1]='\0'; pt->kind=port_string|port_output|port_srfi6; pt->rep.string.start=start; pt->rep.string.curr=start; pt->rep.string.past_the_end=start+BLOCK_SIZE-1; port_init_location(sc, pt, NULL); return pt; } static pointer port_from_scratch(scheme *sc) { port *pt; pt=port_rep_from_scratch(sc); if(pt==0) { return sc->NIL; } return mk_port(sc,pt); } static void port_close(scheme *sc, pointer p, int flag) { port *pt=p->_object._port; pt->kind&=~flag; if((pt->kind & (port_input|port_output))==0) { /* Cleanup is here so (close-*-port) functions could work too */ port_clear_location(sc, pt); if(pt->kind&port_file) { fclose(pt->rep.stdio.file); } pt->kind=port_free; } } /* get new character from input file */ static int inchar(scheme *sc) { int c; port *pt; pt = sc->inport->_object._port; if(pt->kind & port_saw_EOF) { return EOF; } c = basic_inchar(pt); if(c == EOF && sc->inport == sc->loadport) { /* Instead, set port_saw_EOF */ pt->kind |= port_saw_EOF; /* file_pop(sc); */ return EOF; /* NOTREACHED */ } return c; } static int basic_inchar(port *pt) { if(pt->kind & port_file) { return fgetc(pt->rep.stdio.file); } else { if(*pt->rep.string.curr == 0 || pt->rep.string.curr == pt->rep.string.past_the_end) { return EOF; } else { return *pt->rep.string.curr++; } } } /* back character to input buffer */ static void backchar(scheme *sc, int c) { port *pt; if(c==EOF) return; pt=sc->inport->_object._port; if(pt->kind&port_file) { ungetc(c,pt->rep.stdio.file); } else { if(pt->rep.string.curr!=pt->rep.string.start) { --pt->rep.string.curr; } } } static int realloc_port_string(scheme *sc, port *p) { char *start=p->rep.string.start; size_t old_size = p->rep.string.past_the_end - start; size_t new_size=p->rep.string.past_the_end-start+1+BLOCK_SIZE; char *str=sc->malloc(new_size); if(str) { memset(str,' ',new_size-1); str[new_size-1]='\0'; memcpy(str, start, old_size); p->rep.string.start=str; p->rep.string.past_the_end=str+new_size-1; p->rep.string.curr-=start-str; sc->free(start); return 1; } else { return 0; } } INTERFACE void putstr(scheme *sc, const char *s) { port *pt=sc->outport->_object._port; if(pt->kind&port_file) { fputs(s,pt->rep.stdio.file); } else { for(;*s;s++) { if(pt->rep.string.curr!=pt->rep.string.past_the_end) { *pt->rep.string.curr++=*s; } else if(pt->kind&port_srfi6&&realloc_port_string(sc,pt)) { *pt->rep.string.curr++=*s; } } } } static void putchars(scheme *sc, const char *s, int len) { port *pt=sc->outport->_object._port; if(pt->kind&port_file) { fwrite(s,1,len,pt->rep.stdio.file); } else { for(;len;len--) { if(pt->rep.string.curr!=pt->rep.string.past_the_end) { *pt->rep.string.curr++=*s++; } else if(pt->kind&port_srfi6&&realloc_port_string(sc,pt)) { *pt->rep.string.curr++=*s++; } } } } INTERFACE void putcharacter(scheme *sc, int c) { port *pt=sc->outport->_object._port; if(pt->kind&port_file) { fputc(c,pt->rep.stdio.file); } else { if(pt->rep.string.curr!=pt->rep.string.past_the_end) { *pt->rep.string.curr++=c; } else if(pt->kind&port_srfi6&&realloc_port_string(sc,pt)) { *pt->rep.string.curr++=c; } } } /* read characters up to delimiter, but cater to character constants */ static char *readstr_upto(scheme *sc, char *delim) { char *p = sc->strbuff; while ((p - sc->strbuff < sc->strbuff_size) && !is_one_of(delim, (*p++ = inchar(sc)))); if(p == sc->strbuff+2 && p[-2] == '\\') { *p=0; } else { backchar(sc,p[-1]); *--p = '\0'; } return sc->strbuff; } /* read string expression "xxx...xxx" */ static pointer readstrexp(scheme *sc) { char *p = sc->strbuff; int c; int c1=0; enum { st_ok, st_bsl, st_x1, st_x2, st_oct1, st_oct2 } state=st_ok; for (;;) { c=inchar(sc); if(c == EOF) { return sc->F; } if(p-sc->strbuff > (sc->strbuff_size)-1) { ptrdiff_t offset = p - sc->strbuff; if (expand_strbuff(sc) != 0) { return sc->F; } p = sc->strbuff + offset; } switch(state) { case st_ok: switch(c) { case '\\': state=st_bsl; break; case '"': *p=0; return mk_counted_string(sc,sc->strbuff,p-sc->strbuff); default: *p++=c; break; } break; case st_bsl: switch(c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': state=st_oct1; c1=c-'0'; break; case 'x': case 'X': state=st_x1; c1=0; break; case 'n': *p++='\n'; state=st_ok; break; case 't': *p++='\t'; state=st_ok; break; case 'r': *p++='\r'; state=st_ok; break; case '"': *p++='"'; state=st_ok; break; default: *p++=c; state=st_ok; break; } break; case st_x1: case st_x2: c=toupper(c); if(c>='0' && c<='F') { if(c<='9') { c1=(c1<<4)+c-'0'; } else { c1=(c1<<4)+c-'A'+10; } if(state==st_x1) { state=st_x2; } else { *p++=c1; state=st_ok; } } else { return sc->F; } break; case st_oct1: case st_oct2: if (c < '0' || c > '7') { *p++=c1; backchar(sc, c); state=st_ok; } else { if (state==st_oct2 && c1 >= 32) return sc->F; c1=(c1<<3)+(c-'0'); if (state == st_oct1) state=st_oct2; else { *p++=c1; state=st_ok; } } break; } } } /* check c is in chars */ static INLINE int is_one_of(char *s, int c) { if(c==EOF) return 1; while (*s) if (*s++ == c) return (1); return (0); } /* skip white characters */ static INLINE int skipspace(scheme *sc) { int c = 0, curr_line = 0; do { c=inchar(sc); #if SHOW_ERROR_LINE if(c=='\n') curr_line++; #endif } while (isspace(c)); /* record it */ port_increment_current_line(sc, &sc->load_stack[sc->file_i], curr_line); if(c!=EOF) { backchar(sc,c); return 1; } else { return EOF; } } /* get token */ static int token(scheme *sc) { int c; c = skipspace(sc); if(c == EOF) { return (TOK_EOF); } switch (c=inchar(sc)) { case EOF: return (TOK_EOF); case '(': return (TOK_LPAREN); case ')': return (TOK_RPAREN); case '.': c=inchar(sc); if(is_one_of(" \n\t",c)) { return (TOK_DOT); } else { backchar(sc,c); backchar(sc,'.'); return TOK_ATOM; } case '\'': return (TOK_QUOTE); case ';': while ((c=inchar(sc)) != '\n' && c!=EOF) ; if(c == '\n') port_increment_current_line(sc, &sc->load_stack[sc->file_i], 1); if(c == EOF) { return (TOK_EOF); } else { return (token(sc));} case '"': return (TOK_DQUOTE); case BACKQUOTE: return (TOK_BQUOTE); case ',': if ((c=inchar(sc)) == '@') { return (TOK_ATMARK); } else { backchar(sc,c); return (TOK_COMMA); } case '#': c=inchar(sc); if (c == '(') { return (TOK_VEC); } else if(c == '!') { while ((c=inchar(sc)) != '\n' && c!=EOF) ; if(c == '\n') port_increment_current_line(sc, &sc->load_stack[sc->file_i], 1); if(c == EOF) { return (TOK_EOF); } else { return (token(sc));} } else { backchar(sc,c); if(is_one_of(" tfodxb\\",c)) { return TOK_SHARP_CONST; } else { return (TOK_SHARP); } } default: backchar(sc,c); return (TOK_ATOM); } } /* ========== Routines for Printing ========== */ #define ok_abbrev(x) (is_pair(x) && cdr(x) == sc->NIL) static void printslashstring(scheme *sc, char *p, int len) { int i; unsigned char *s=(unsigned char*)p; putcharacter(sc,'"'); for ( i=0; iNIL) { p = "()"; } else if (l == sc->T) { p = "#t"; } else if (l == sc->F) { p = "#f"; } else if (l == sc->EOF_OBJ) { p = "#"; } else if (is_port(l)) { p = "#"; } else if (is_number(l)) { p = sc->strbuff; if (f <= 1 || f == 10) /* f is the base for numbers if > 1 */ { if(num_is_integer(l)) { snprintf(p, STRBUFFSIZE, "%ld", ivalue_unchecked(l)); } else { snprintf(p, STRBUFFSIZE, "%.10g", rvalue_unchecked(l)); /* r5rs says there must be a '.' (unless 'e'?) */ f = strcspn(p, ".e"); if (p[f] == 0) { p[f] = '.'; /* not found, so add '.0' at the end */ p[f+1] = '0'; p[f+2] = 0; } } } else { long v = ivalue(l); if (f == 16) { if (v >= 0) snprintf(p, STRBUFFSIZE, "%lx", v); else snprintf(p, STRBUFFSIZE, "-%lx", -v); } else if (f == 8) { if (v >= 0) snprintf(p, STRBUFFSIZE, "%lo", v); else snprintf(p, STRBUFFSIZE, "-%lo", -v); } else if (f == 2) { unsigned long b = (v < 0) ? -v : v; p = &p[STRBUFFSIZE-1]; *p = 0; do { *--p = (b&1) ? '1' : '0'; b >>= 1; } while (b != 0); if (v < 0) *--p = '-'; } } } else if (is_string(l)) { if (!f) { *pp = strvalue(l); *plen = strlength(l); return; } else { /* Hack, uses the fact that printing is needed */ *pp=sc->strbuff; *plen=0; printslashstring(sc, strvalue(l), strlength(l)); return; } } else if (is_character(l)) { int c=charvalue(l); p = sc->strbuff; if (!f) { p[0]=c; p[1]=0; } else { switch(c) { case ' ': p = "#\\space"; break; case '\n': p = "#\\newline"; break; case '\r': p = "#\\return"; break; case '\t': p = "#\\tab"; break; default: #if USE_ASCII_NAMES if(c==127) { p = "#\\del"; break; } else if(c<32) { snprintf(p,STRBUFFSIZE, "#\\%s",charnames[c]); break; } #else if(c<32) { snprintf(p,STRBUFFSIZE,"#\\x%x",c); break; } #endif snprintf(p,STRBUFFSIZE,"#\\%c",c); break; } } } else if (is_symbol(l)) { p = symname(l); } else if (is_proc(l)) { p = sc->strbuff; snprintf(p,STRBUFFSIZE,"#<%s PROCEDURE %ld>", procname(l),procnum(l)); } else if (is_macro(l)) { p = "#"; } else if (is_closure(l)) { p = "#"; } else if (is_promise(l)) { p = "#"; } else if (is_foreign(l)) { p = sc->strbuff; snprintf(p,STRBUFFSIZE,"#", procnum(l)); } else if (is_continuation(l)) { p = "#"; } else if (is_foreign_object(l)) { p = sc->strbuff; l->_object._foreign_object._vtable->to_string(sc, p, STRBUFFSIZE, l->_object._foreign_object._data); } else { p = "#"; } *pp=p; *plen=strlen(p); } /* ========== Routines for Evaluation Cycle ========== */ /* make closure. c is code. e is environment */ static pointer mk_closure(scheme *sc, pointer c, pointer e) { pointer x = get_cell(sc, c, e); typeflag(x) = T_CLOSURE; car(x) = c; cdr(x) = e; return (x); } /* make continuation. */ static pointer mk_continuation(scheme *sc, pointer d) { pointer x = get_cell(sc, sc->NIL, d); typeflag(x) = T_CONTINUATION; cont_dump(x) = d; return (x); } static pointer list_star(scheme *sc, pointer d) { pointer p, q; if(cdr(d)==sc->NIL) { return car(d); } p=cons(sc,car(d),cdr(d)); q=p; while(cdr(cdr(p))!=sc->NIL) { d=cons(sc,car(p),cdr(p)); if(cdr(cdr(p))!=sc->NIL) { p=cdr(d); } } cdr(p)=car(cdr(p)); return q; } /* reverse list -- produce new list */ static pointer reverse(scheme *sc, pointer term, pointer list) { /* a must be checked by gc */ pointer a = list, p = term; for ( ; is_pair(a); a = cdr(a)) { p = cons(sc, car(a), p); } return (p); } /* reverse list --- in-place */ static pointer reverse_in_place(scheme *sc, pointer term, pointer list) { pointer p = list, result = term, q; while (p != sc->NIL) { q = cdr(p); cdr(p) = result; result = p; p = q; } return (result); } /* append list -- produce new list (in reverse order) */ static pointer revappend(scheme *sc, pointer a, pointer b) { pointer result = a; pointer p = b; while (is_pair(p)) { result = cons(sc, car(p), result); p = cdr(p); } if (p == sc->NIL) { return result; } return sc->F; /* signal an error */ } /* equivalence of atoms */ int eqv(pointer a, pointer b) { if (is_string(a)) { if (is_string(b)) return (strvalue(a) == strvalue(b)); else return (0); } else if (is_number(a)) { if (is_number(b)) { if (num_is_integer(a) == num_is_integer(b)) return num_eq(nvalue(a),nvalue(b)); } return (0); } else if (is_character(a)) { if (is_character(b)) return charvalue(a)==charvalue(b); else return (0); } else if (is_port(a)) { if (is_port(b)) return a==b; else return (0); } else if (is_proc(a)) { if (is_proc(b)) return procnum(a)==procnum(b); else return (0); } else { return (a == b); } } /* true or false value macro */ /* () is #t in R5RS */ #define is_true(p) ((p) != sc->F) #define is_false(p) ((p) == sc->F) /* ========== Environment implementation ========== */ #if !defined(USE_ALIST_ENV) || !defined(USE_OBJECT_LIST) static int hash_fn(const char *key, int table_size) { unsigned int hashed = 0; const char *c; int bits_per_int = sizeof(unsigned int)*8; for (c = key; *c; c++) { /* letters have about 5 bits in them */ hashed = (hashed<<5) | (hashed>>(bits_per_int-5)); hashed ^= *c; } return hashed % table_size; } #endif /* Compares A and B. Returns an integer less than, equal to, or * greater than zero if A is stored at a memory location that is * numerical less than, equal to, or greater than that of B. */ static int pointercmp(pointer a, pointer b) { uintptr_t a_n = (uintptr_t) a; uintptr_t b_n = (uintptr_t) b; if (a_n < b_n) return -1; if (a_n > b_n) return 1; return 0; } #ifndef USE_ALIST_ENV /* * In this implementation, each frame of the environment may be * a hash table: a vector of alists hashed by variable name. * In practice, we use a vector only for the initial frame; * subsequent frames are too small and transient for the lookup * speed to out-weigh the cost of making a new vector. */ static void new_frame_in_env(scheme *sc, pointer old_env) { pointer new_frame; /* The interaction-environment has about 480 variables in it. */ if (old_env == sc->NIL) { new_frame = mk_vector(sc, 751); } else { new_frame = sc->NIL; } gc_disable(sc, 1); sc->envir = immutable_cons(sc, new_frame, old_env); gc_enable(sc); setenvironment(sc->envir); } /* Find the slot in ENV under the key HDL. If ALL is given, look in * all environments enclosing ENV. If the lookup fails, and SSLOT is * given, the position where the new slot has to be inserted is stored * at SSLOT. */ static pointer find_slot_spec_in_env(scheme *sc, pointer env, pointer hdl, int all, pointer **sslot) { pointer x,y; int location; pointer *sl; int d; assert(is_symbol(hdl)); for (x = env; x != sc->NIL; x = cdr(x)) { if (is_vector(car(x))) { location = hash_fn(symname(hdl), vector_length(car(x))); sl = vector_elem_slot(car(x), location); } else { sl = &car(x); } for (y = *sl ; y != sc->NIL; sl = &cdr(y), y = *sl) { d = pointercmp(caar(y), hdl); if (d == 0) return car(y); /* Hit. */ else if (d > 0) break; /* Miss. */ } if (x == env && sslot) *sslot = sl; /* Insert here. */ if (!all) return sc->NIL; /* Miss, and stop looking. */ } return sc->NIL; /* Not found in any environment. */ } #else /* USE_ALIST_ENV */ static INLINE void new_frame_in_env(scheme *sc, pointer old_env) { sc->envir = immutable_cons(sc, sc->NIL, old_env); setenvironment(sc->envir); } /* Find the slot in ENV under the key HDL. If ALL is given, look in * all environments enclosing ENV. If the lookup fails, and SSLOT is * given, the position where the new slot has to be inserted is stored * at SSLOT. */ static pointer find_slot_spec_in_env(scheme *sc, pointer env, pointer hdl, int all, pointer **sslot) { pointer x,y; pointer *sl; int d; assert(is_symbol(hdl)); for (x = env; x != sc->NIL; x = cdr(x)) { for (sl = &car(x), y = *sl; y != sc->NIL; sl = &cdr(y), y = *sl) { d = pointercmp(caar(y), hdl); if (d == 0) return car(y); /* Hit. */ else if (d > 0) break; /* Miss. */ } if (x == env && sslot) *sslot = sl; /* Insert here. */ if (!all) return sc->NIL; /* Miss, and stop looking. */ } return sc->NIL; /* Not found in any environment. */ } #endif /* USE_ALIST_ENV else */ static pointer find_slot_in_env(scheme *sc, pointer env, pointer hdl, int all) { return find_slot_spec_in_env(sc, env, hdl, all, NULL); } /* Insert (VARIABLE, VALUE) at SSLOT. SSLOT must be obtained using * find_slot_spec_in_env, and no insertion must be done between * obtaining SSLOT and the call to this function. */ static INLINE void new_slot_spec_in_env(scheme *sc, pointer variable, pointer value, pointer *sslot) { #define new_slot_spec_in_env_allocates 2 pointer slot; gc_disable(sc, gc_reservations (new_slot_spec_in_env)); slot = immutable_cons(sc, variable, value); *sslot = immutable_cons(sc, slot, *sslot); gc_enable(sc); } static INLINE void new_slot_in_env(scheme *sc, pointer variable, pointer value) { #define new_slot_in_env_allocates new_slot_spec_in_env_allocates pointer slot; pointer *sslot; assert(is_symbol(variable)); slot = find_slot_spec_in_env(sc, sc->envir, variable, 0, &sslot); assert(slot == sc->NIL); new_slot_spec_in_env(sc, variable, value, sslot); } static INLINE void set_slot_in_env(scheme *sc, pointer slot, pointer value) { (void)sc; cdr(slot) = value; } static INLINE pointer slot_value_in_env(pointer slot) { return cdr(slot); } /* ========== Evaluation Cycle ========== */ static enum scheme_opcodes _Error_1(scheme *sc, const char *s, pointer a) { const char *str = s; pointer history; #if USE_ERROR_HOOK pointer x; pointer hdl=sc->ERROR_HOOK; #endif #if SHOW_ERROR_LINE char sbuf[STRBUFFSIZE]; #endif history = history_flatten(sc); #if SHOW_ERROR_LINE /* make sure error is not in REPL */ if (((sc->load_stack[sc->file_i].kind & port_file) == 0 || sc->load_stack[sc->file_i].rep.stdio.file != stdin)) { pointer tag; const char *fname; int ln; if (history != sc->NIL && has_tag(car(history)) && (tag = get_tag(sc, car(history))) && is_string(car(tag)) && is_integer(cdr(tag))) { fname = string_value(car(tag)); ln = ivalue_unchecked(cdr(tag)); } else { fname = string_value(sc->load_stack[sc->file_i].filename); ln = ivalue_unchecked(sc->load_stack[sc->file_i].curr_line); } /* should never happen */ if(!fname) fname = ""; /* we started from 0 */ ln++; snprintf(sbuf, STRBUFFSIZE, "%s:%i: %s", fname, ln, s); str = (const char*)sbuf; } #endif #if USE_ERROR_HOOK x=find_slot_in_env(sc,sc->envir,hdl,1); if (x != sc->NIL) { sc->code = cons(sc, cons(sc, sc->QUOTE, cons(sc, history, sc->NIL)), sc->NIL); if(a!=0) { sc->code = cons(sc, cons(sc, sc->QUOTE, cons(sc, a, sc->NIL)), sc->code); } else { sc->code = cons(sc, sc->F, sc->code); } sc->code = cons(sc, mk_string(sc, str), sc->code); setimmutable(car(sc->code)); sc->code = cons(sc, slot_value_in_env(x), sc->code); return OP_EVAL; } #endif if(a!=0) { sc->args = cons(sc, (a), sc->NIL); } else { sc->args = sc->NIL; } sc->args = cons(sc, mk_string(sc, str), sc->args); setimmutable(car(sc->args)); return OP_ERR0; } #define Error_1(sc,s, a) { op = _Error_1(sc,s,a); goto dispatch; } #define Error_0(sc,s) { op = _Error_1(sc,s,0); goto dispatch; } /* Too small to turn into function */ # define BEGIN do { # define END } while (0) /* Flags. The interpreter has a flags field. When the interpreter * pushes a frame to the dump stack, it is encoded with the opcode. * Therefore, we do not use the least significant byte. */ /* Masks used to encode and decode opcode and flags. */ #define S_OP_MASK 0x000000ff #define S_FLAG_MASK 0xffffff00 /* Set if the interpreter evaluates an expression in a tail context * (see R5RS, section 3.5). If a function, procedure, or continuation * is invoked while this flag is set, the call is recorded as tail * call in the history buffer. */ #define S_FLAG_TAIL_CONTEXT 0x00000100 /* Set flag F. */ #define s_set_flag(sc, f) \ BEGIN \ (sc)->flags |= S_FLAG_ ## f; \ END /* Clear flag F. */ #define s_clear_flag(sc, f) \ BEGIN \ (sc)->flags &= ~ S_FLAG_ ## f; \ END /* Check if flag F is set. */ #define s_get_flag(sc, f) \ !!((sc)->flags & S_FLAG_ ## f) /* Bounce back to Eval_Cycle and execute A. */ #define s_goto(sc, a) { op = (a); goto dispatch; } #if USE_THREADED_CODE /* Do not bounce back to Eval_Cycle but execute A by jumping directly * to it. */ #define s_thread_to(sc, a) \ BEGIN \ op = (a); \ goto a; \ END /* Define a label OP and emit a case statement for OP. For use in the * dispatch function. The slightly peculiar goto that is never * executed avoids warnings about unused labels. */ #if __GNUC__ > 6 #define CASE(OP) OP: __attribute__((unused)); case OP #else #define CASE(OP) case OP: if (0) goto OP; OP #endif #else /* USE_THREADED_CODE */ #define s_thread_to(sc, a) s_goto(sc, a) #define CASE(OP) case OP #endif /* USE_THREADED_CODE */ #if __GNUC__ > 6 #define FALLTHROUGH __attribute__ ((fallthrough)) #else #define FALLTHROUGH /* fallthrough */ #endif /* Return to the previous frame on the dump stack, setting the current * value to A. */ #define s_return(sc, a) s_goto(sc, _s_return(sc, a, 0)) /* Return to the previous frame on the dump stack, setting the current * value to A, and re-enable the garbage collector. */ #define s_return_enable_gc(sc, a) s_goto(sc, _s_return(sc, a, 1)) static INLINE void dump_stack_reset(scheme *sc) { sc->dump = sc->NIL; } static INLINE void dump_stack_initialize(scheme *sc) { dump_stack_reset(sc); sc->frame_freelist = sc->NIL; } static void dump_stack_free(scheme *sc) { dump_stack_initialize(sc); } const int frame_length = 4; static pointer dump_stack_make_frame(scheme *sc) { pointer frame; frame = mk_vector(sc, frame_length); if (! sc->no_memory) setframe(frame); return frame; } static INLINE pointer * frame_slots(pointer frame) { return &frame->_object._vector._elements[0]; } #define frame_payload vector_length static pointer dump_stack_allocate_frame(scheme *sc) { pointer frame = sc->frame_freelist; if (frame == sc->NIL) { if (gc_enabled(sc)) frame = dump_stack_make_frame(sc); else gc_reservation_failure(sc); } else sc->frame_freelist = *frame_slots(frame); return frame; } static void dump_stack_deallocate_frame(scheme *sc, pointer frame) { pointer *p = frame_slots(frame); *p++ = sc->frame_freelist; *p++ = sc->NIL; *p++ = sc->NIL; *p++ = sc->NIL; sc->frame_freelist = frame; } static void dump_stack_preallocate_frame(scheme *sc) { pointer frame = dump_stack_make_frame(sc); if (! sc->no_memory) dump_stack_deallocate_frame(sc, frame); } static enum scheme_opcodes _s_return(scheme *sc, pointer a, int enable_gc) { pointer dump = sc->dump; pointer *p; unsigned long v; enum scheme_opcodes next_op; sc->value = (a); if (enable_gc) gc_enable(sc); if (dump == sc->NIL) return OP_QUIT; v = frame_payload(dump); next_op = (int) (v & S_OP_MASK); sc->flags = v & S_FLAG_MASK; p = frame_slots(dump); sc->args = *p++; sc->envir = *p++; sc->code = *p++; sc->dump = *p++; dump_stack_deallocate_frame(sc, dump); return next_op; } static void s_save(scheme *sc, enum scheme_opcodes op, pointer args, pointer code) { #define s_save_allocates 0 pointer dump; pointer *p; gc_disable(sc, gc_reservations (s_save)); dump = dump_stack_allocate_frame(sc); frame_payload(dump) = (size_t) (sc->flags | (unsigned long) op); p = frame_slots(dump); *p++ = args; *p++ = sc->envir; *p++ = code; *p++ = sc->dump; sc->dump = dump; gc_enable(sc); } static INLINE void dump_stack_mark(scheme *sc) { mark(sc->dump); mark(sc->frame_freelist); } #if USE_HISTORY static void history_free(scheme *sc) { sc->free(sc->history.m); sc->history.tailstacks = sc->NIL; sc->history.callstack = sc->NIL; } static pointer history_init(scheme *sc, size_t N, size_t M) { size_t i; struct history *h = &sc->history; h->N = N; h->mask_N = N - 1; h->n = N - 1; assert ((N & h->mask_N) == 0); h->M = M; h->mask_M = M - 1; assert ((M & h->mask_M) == 0); h->callstack = mk_vector(sc, N); if (h->callstack == sc->sink) goto fail; h->tailstacks = mk_vector(sc, N); for (i = 0; i < N; i++) { pointer tailstack = mk_vector(sc, M); if (tailstack == sc->sink) goto fail; set_vector_elem(h->tailstacks, i, tailstack); } h->m = sc->malloc(N * sizeof *h->m); if (h->m == NULL) goto fail; for (i = 0; i < N; i++) h->m[i] = 0; return sc->T; fail: history_free(sc); return sc->F; } static void history_mark(scheme *sc) { struct history *h = &sc->history; mark(h->callstack); mark(h->tailstacks); } #define add_mod(a, b, mask) (((a) + (b)) & (mask)) #define sub_mod(a, b, mask) add_mod(a, (mask) + 1 - (b), mask) static INLINE void tailstack_clear(scheme *sc, pointer v) { assert(is_vector(v)); /* XXX optimize */ fill_vector(v, sc->NIL); } static pointer callstack_pop(scheme *sc) { struct history *h = &sc->history; size_t n = h->n; pointer item; if (h->callstack == sc->NIL) return sc->NIL; item = vector_elem(h->callstack, n); /* Clear our frame so that it can be gc'ed and we don't run into it * when walking the history. */ set_vector_elem(h->callstack, n, sc->NIL); tailstack_clear(sc, vector_elem(h->tailstacks, n)); /* Exit from the frame. */ h->n = sub_mod(h->n, 1, h->mask_N); return item; } static void callstack_push(scheme *sc, pointer item) { struct history *h = &sc->history; size_t n = h->n; if (h->callstack == sc->NIL) return; /* Enter a new frame. */ n = h->n = add_mod(n, 1, h->mask_N); /* Initialize tail stack. */ tailstack_clear(sc, vector_elem(h->tailstacks, n)); h->m[n] = h->mask_M; set_vector_elem(h->callstack, n, item); } static void tailstack_push(scheme *sc, pointer item) { struct history *h = &sc->history; size_t n = h->n; size_t m = h->m[n]; if (h->callstack == sc->NIL) return; /* Enter a new tail frame. */ m = h->m[n] = add_mod(m, 1, h->mask_M); set_vector_elem(vector_elem(h->tailstacks, n), m, item); } static pointer tailstack_flatten(scheme *sc, pointer tailstack, size_t i, size_t n, pointer acc) { struct history *h = &sc->history; pointer frame; assert(i <= h->M); assert(n < h->M); if (acc == sc->sink) return sc->sink; if (i == 0) { /* We reached the end, but we did not see a unused frame. Signal this using '... . */ return cons(sc, mk_symbol(sc, "..."), acc); } frame = vector_elem(tailstack, n); if (frame == sc->NIL) { /* A unused frame. We reached the end of the history. */ return acc; } /* Add us. */ acc = cons(sc, frame, acc); return tailstack_flatten(sc, tailstack, i - 1, sub_mod(n, 1, h->mask_M), acc); } static pointer callstack_flatten(scheme *sc, size_t i, size_t n, pointer acc) { struct history *h = &sc->history; pointer frame; assert(i <= h->N); assert(n < h->N); if (acc == sc->sink) return sc->sink; if (i == 0) { /* We reached the end, but we did not see a unused frame. Signal this using '... . */ return cons(sc, mk_symbol(sc, "..."), acc); } frame = vector_elem(h->callstack, n); if (frame == sc->NIL) { /* A unused frame. We reached the end of the history. */ return acc; } /* First, emit the tail calls. */ acc = tailstack_flatten(sc, vector_elem(h->tailstacks, n), h->M, h->m[n], acc); /* Then us. */ acc = cons(sc, frame, acc); return callstack_flatten(sc, i - 1, sub_mod(n, 1, h->mask_N), acc); } static pointer history_flatten(scheme *sc) { struct history *h = &sc->history; pointer history; if (h->callstack == sc->NIL) return sc->NIL; history = callstack_flatten(sc, h->N, h->n, sc->NIL); if (history == sc->sink) return sc->sink; return reverse_in_place(sc, sc->NIL, history); } #undef add_mod #undef sub_mod #else /* USE_HISTORY */ #define history_init(SC, A, B) (void) 0 #define history_free(SC) (void) 0 #define callstack_pop(SC) (void) 0 #define callstack_push(SC, X) (void) 0 #define tailstack_push(SC, X) (void) 0 #endif /* USE_HISTORY */ #if USE_PLIST static pointer get_property(scheme *sc, pointer obj, pointer key) { pointer x; assert (is_symbol(obj)); assert (is_symbol(key)); for (x = symprop(obj); x != sc->NIL; x = cdr(x)) { if (caar(x) == key) break; } if (x != sc->NIL) return cdar(x); return sc->NIL; } static pointer set_property(scheme *sc, pointer obj, pointer key, pointer value) { #define set_property_allocates 2 pointer x; assert (is_symbol(obj)); assert (is_symbol(key)); for (x = symprop(obj); x != sc->NIL; x = cdr(x)) { if (caar(x) == key) break; } if (x != sc->NIL) cdar(x) = value; else { gc_disable(sc, gc_reservations(set_property)); symprop(obj) = cons(sc, cons(sc, key, value), symprop(obj)); gc_enable(sc); } return sc->T; } #endif static int is_list(scheme *sc, pointer a) { return list_length(sc,a) >= 0; } /* Result is: proper list: length circular list: -1 not even a pair: -2 dotted list: -2 minus length before dot */ int list_length(scheme *sc, pointer a) { int i=0; pointer slow, fast; slow = fast = a; while (1) { if (fast == sc->NIL) return i; if (!is_pair(fast)) return -2 - i; fast = cdr(fast); ++i; if (fast == sc->NIL) return i; if (!is_pair(fast)) return -2 - i; ++i; fast = cdr(fast); /* Safe because we would have already returned if `fast' encountered a non-pair. */ slow = cdr(slow); if (fast == slow) { /* the fast pointer has looped back around and caught up with the slow pointer, hence the structure is circular, not of finite length, and therefore not a list */ return -1; } } } #define s_retbool(tf) s_return(sc,(tf) ? sc->T : sc->F) /* kernel of this interpreter */ static void Eval_Cycle(scheme *sc, enum scheme_opcodes op) { for (;;) { pointer x, y; pointer callsite; num v; #if USE_MATH double dd; #endif int (*comp_func)(num, num) = NULL; const struct op_code_info *pcd; dispatch: pcd = &dispatch_table[op]; if (pcd->name[0] != 0) { /* if built-in function, check arguments */ char msg[STRBUFFSIZE]; if (! check_arguments (sc, pcd, msg, sizeof msg)) { s_goto(sc, _Error_1(sc, msg, 0)); } } if(sc->no_memory) { fprintf(stderr,"No memory!\n"); exit(1); } ok_to_freely_gc(sc); switch (op) { CASE(OP_LOAD): /* load */ if(file_interactive(sc)) { fprintf(sc->outport->_object._port->rep.stdio.file, "Loading %s\n", strvalue(car(sc->args))); } if (!file_push(sc, car(sc->args))) { Error_1(sc,"unable to open", car(sc->args)); } else { sc->args = mk_integer(sc,sc->file_i); s_thread_to(sc,OP_T0LVL); } CASE(OP_T0LVL): /* top level */ /* If we reached the end of file, this loop is done. */ if(sc->loadport->_object._port->kind & port_saw_EOF) { if(sc->file_i == 0) { sc->args=sc->NIL; sc->nesting = sc->nesting_stack[0]; s_thread_to(sc,OP_QUIT); } else { file_pop(sc); s_return(sc,sc->value); } /* NOTREACHED */ } /* If interactive, be nice to user. */ if(file_interactive(sc)) { sc->envir = sc->global_env; dump_stack_reset(sc); putstr(sc,"\n"); putstr(sc,prompt); } /* Set up another iteration of REPL */ sc->nesting=0; sc->save_inport=sc->inport; sc->inport = sc->loadport; s_save(sc,OP_T0LVL, sc->NIL, sc->NIL); s_save(sc,OP_VALUEPRINT, sc->NIL, sc->NIL); s_save(sc,OP_T1LVL, sc->NIL, sc->NIL); s_thread_to(sc,OP_READ_INTERNAL); CASE(OP_T1LVL): /* top level */ sc->code = sc->value; sc->inport=sc->save_inport; s_thread_to(sc,OP_EVAL); CASE(OP_READ_INTERNAL): /* internal read */ sc->tok = token(sc); if(sc->tok==TOK_EOF) { s_return(sc,sc->EOF_OBJ); } s_thread_to(sc,OP_RDSEXPR); CASE(OP_GENSYM): s_return(sc, gensym(sc)); CASE(OP_VALUEPRINT): /* print evaluation result */ /* OP_VALUEPRINT is always pushed, because when changing from non-interactive to interactive mode, it needs to be already on the stack */ if(sc->tracing) { putstr(sc,"\nGives: "); } if(file_interactive(sc)) { sc->print_flag = 1; sc->args = sc->value; s_thread_to(sc,OP_P0LIST); } else { s_return(sc,sc->value); } CASE(OP_EVAL): /* main part of evaluation */ #if USE_TRACING if(sc->tracing) { /*s_save(sc,OP_VALUEPRINT,sc->NIL,sc->NIL);*/ s_save(sc,OP_REAL_EVAL,sc->args,sc->code); sc->args=sc->code; putstr(sc,"\nEval: "); s_thread_to(sc,OP_P0LIST); } FALLTHROUGH; CASE(OP_REAL_EVAL): #endif if (is_symbol(sc->code)) { /* symbol */ x=find_slot_in_env(sc,sc->envir,sc->code,1); if (x != sc->NIL) { s_return(sc,slot_value_in_env(x)); } else { Error_1(sc, "eval: unbound variable", sc->code); } } else if (is_pair(sc->code)) { if (is_syntax(x = car(sc->code))) { /* SYNTAX */ sc->code = cdr(sc->code); s_goto(sc, syntaxnum(sc, x)); } else {/* first, eval top element and eval arguments */ s_save(sc,OP_E0ARGS, sc->NIL, sc->code); /* If no macros => s_save(sc,OP_E1ARGS, sc->NIL, cdr(sc->code));*/ sc->code = car(sc->code); s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); } } else { s_return(sc,sc->code); } CASE(OP_E0ARGS): /* eval arguments */ if (is_macro(sc->value)) { /* macro expansion */ gc_disable(sc, 1 + gc_reservations (s_save)); s_save(sc,OP_DOMACRO, sc->NIL, sc->NIL); sc->args = cons(sc,sc->code, sc->NIL); gc_enable(sc); sc->code = sc->value; s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_APPLY); } else { gc_disable(sc, 1); sc->args = cons(sc, sc->code, sc->NIL); gc_enable(sc); sc->code = cdr(sc->code); s_thread_to(sc,OP_E1ARGS); } CASE(OP_E1ARGS): /* eval arguments */ gc_disable(sc, 1); sc->args = cons(sc, sc->value, sc->args); gc_enable(sc); if (is_pair(sc->code)) { /* continue */ s_save(sc,OP_E1ARGS, sc->args, cdr(sc->code)); sc->code = car(sc->code); sc->args = sc->NIL; s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); } else { /* end */ sc->args = reverse_in_place(sc, sc->NIL, sc->args); s_thread_to(sc,OP_APPLY_CODE); } #if USE_TRACING CASE(OP_TRACING): { int tr=sc->tracing; sc->tracing=ivalue(car(sc->args)); gc_disable(sc, 1); s_return_enable_gc(sc, mk_integer(sc, tr)); } #endif #if USE_HISTORY CASE(OP_CALLSTACK_POP): /* pop the call stack */ callstack_pop(sc); s_return(sc, sc->value); #endif CASE(OP_APPLY_CODE): /* apply 'cadr(args)' to 'cddr(args)', * record in the history as invoked from * 'car(args)' */ free_cons(sc, sc->args, &callsite, &sc->args); sc->code = car(sc->args); sc->args = cdr(sc->args); FALLTHROUGH; CASE(OP_APPLY): /* apply 'code' to 'args' */ #if USE_TRACING if(sc->tracing) { s_save(sc,OP_REAL_APPLY,sc->args,sc->code); sc->print_flag = 1; /* sc->args=cons(sc,sc->code,sc->args);*/ putstr(sc,"\nApply to: "); s_thread_to(sc,OP_P0LIST); } FALLTHROUGH; CASE(OP_REAL_APPLY): #endif #if USE_HISTORY if (op != OP_APPLY_CODE) callsite = sc->code; if (s_get_flag(sc, TAIL_CONTEXT)) { /* We are evaluating a tail call. */ tailstack_push(sc, callsite); } else { callstack_push(sc, callsite); s_save(sc, OP_CALLSTACK_POP, sc->NIL, sc->NIL); } #endif if (is_proc(sc->code)) { s_goto(sc,procnum(sc->code)); /* PROCEDURE */ } else if (is_foreign(sc->code)) { /* Keep nested calls from GC'ing the arglist */ push_recent_alloc(sc,sc->args,sc->NIL); x=sc->code->_object._ff(sc,sc->args); s_return(sc,x); } else if (is_closure(sc->code) || is_macro(sc->code) || is_promise(sc->code)) { /* CLOSURE */ /* Should not accept promise */ /* make environment */ new_frame_in_env(sc, closure_env(sc->code)); for (x = car(closure_code(sc->code)), y = sc->args; is_pair(x); x = cdr(x), y = cdr(y)) { if (y == sc->NIL) { Error_1(sc, "not enough arguments, missing", x); } else if (is_symbol(car(x))) { new_slot_in_env(sc, car(x), car(y)); } else { Error_1(sc, "syntax error in closure: not a symbol", car(x)); } } if (x == sc->NIL) { if (y != sc->NIL) { Error_0(sc, "too many arguments"); } } else if (is_symbol(x)) new_slot_in_env(sc, x, y); else { Error_1(sc, "syntax error in closure: not a symbol", x); } sc->code = cdr(closure_code(sc->code)); sc->args = sc->NIL; s_set_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_BEGIN); } else if (is_continuation(sc->code)) { /* CONTINUATION */ sc->dump = cont_dump(sc->code); s_return(sc,sc->args != sc->NIL ? car(sc->args) : sc->NIL); } else { Error_1(sc,"illegal function",sc->code); } CASE(OP_DOMACRO): /* do macro */ sc->code = sc->value; s_thread_to(sc,OP_EVAL); #if USE_COMPILE_HOOK CASE(OP_LAMBDA): /* lambda */ /* If the hook is defined, apply it to sc->code, otherwise set sc->value fall through */ { pointer f=find_slot_in_env(sc,sc->envir,sc->COMPILE_HOOK,1); if(f==sc->NIL) { sc->value = sc->code; /* Fallthru */ } else { gc_disable(sc, 1 + gc_reservations (s_save)); s_save(sc,OP_LAMBDA1,sc->args,sc->code); sc->args=cons(sc,sc->code,sc->NIL); gc_enable(sc); sc->code=slot_value_in_env(f); s_thread_to(sc,OP_APPLY); } } #else CASE(OP_LAMBDA): /* lambda */ sc->value = sc->code; #endif FALLTHROUGH; CASE(OP_LAMBDA1): gc_disable(sc, 1); s_return_enable_gc(sc, mk_closure(sc, sc->value, sc->envir)); CASE(OP_MKCLOSURE): /* make-closure */ x=car(sc->args); if(car(x)==sc->LAMBDA) { x=cdr(x); } if(cdr(sc->args)==sc->NIL) { y=sc->envir; } else { y=cadr(sc->args); } gc_disable(sc, 1); s_return_enable_gc(sc, mk_closure(sc, x, y)); CASE(OP_QUOTE): /* quote */ s_return(sc,car(sc->code)); CASE(OP_DEF0): /* define */ if(is_immutable(car(sc->code))) Error_1(sc,"define: unable to alter immutable", car(sc->code)); if (is_pair(car(sc->code))) { x = caar(sc->code); gc_disable(sc, 2); sc->code = cons(sc, sc->LAMBDA, cons(sc, cdar(sc->code), cdr(sc->code))); gc_enable(sc); } else { x = car(sc->code); sc->code = cadr(sc->code); } if (!is_symbol(x)) { Error_0(sc,"variable is not a symbol"); } s_save(sc,OP_DEF1, sc->NIL, x); s_thread_to(sc,OP_EVAL); CASE(OP_DEF1): { /* define */ pointer *sslot; x = find_slot_spec_in_env(sc, sc->envir, sc->code, 0, &sslot); if (x != sc->NIL) { set_slot_in_env(sc, x, sc->value); } else { new_slot_spec_in_env(sc, sc->code, sc->value, sslot); } s_return(sc,sc->code); } CASE(OP_DEFP): /* defined? */ x=sc->envir; if(cdr(sc->args)!=sc->NIL) { x=cadr(sc->args); } s_retbool(find_slot_in_env(sc,x,car(sc->args),1)!=sc->NIL); CASE(OP_SET0): /* set! */ if(is_immutable(car(sc->code))) Error_1(sc,"set!: unable to alter immutable variable",car(sc->code)); s_save(sc,OP_SET1, sc->NIL, car(sc->code)); sc->code = cadr(sc->code); s_thread_to(sc,OP_EVAL); CASE(OP_SET1): /* set! */ y=find_slot_in_env(sc,sc->envir,sc->code,1); if (y != sc->NIL) { set_slot_in_env(sc, y, sc->value); s_return(sc,sc->value); } else { Error_1(sc, "set!: unbound variable", sc->code); } CASE(OP_BEGIN): /* begin */ { int last; if (!is_pair(sc->code)) { s_return(sc,sc->code); } last = cdr(sc->code) == sc->NIL; if (!last) { s_save(sc,OP_BEGIN, sc->NIL, cdr(sc->code)); } sc->code = car(sc->code); if (! last) /* This is not the end of the list. This is not a tail * position. */ s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); } CASE(OP_IF0): /* if */ s_save(sc,OP_IF1, sc->NIL, cdr(sc->code)); sc->code = car(sc->code); s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); CASE(OP_IF1): /* if */ if (is_true(sc->value)) sc->code = car(sc->code); else sc->code = cadr(sc->code); /* (if #f 1) ==> () because * car(sc->NIL) = sc->NIL */ s_thread_to(sc,OP_EVAL); CASE(OP_LET0): /* let */ sc->args = sc->NIL; sc->value = sc->code; sc->code = is_symbol(car(sc->code)) ? cadr(sc->code) : car(sc->code); s_thread_to(sc,OP_LET1); CASE(OP_LET1): /* let (calculate parameters) */ gc_disable(sc, 1 + (is_pair(sc->code) ? gc_reservations (s_save) : 0)); sc->args = cons(sc, sc->value, sc->args); if (is_pair(sc->code)) { /* continue */ if (!is_pair(car(sc->code)) || !is_pair(cdar(sc->code))) { gc_enable(sc); Error_1(sc, "Bad syntax of binding spec in let", car(sc->code)); } s_save(sc,OP_LET1, sc->args, cdr(sc->code)); gc_enable(sc); sc->code = cadar(sc->code); sc->args = sc->NIL; s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); } else { /* end */ gc_enable(sc); sc->args = reverse_in_place(sc, sc->NIL, sc->args); sc->code = car(sc->args); sc->args = cdr(sc->args); s_thread_to(sc,OP_LET2); } CASE(OP_LET2): /* let */ new_frame_in_env(sc, sc->envir); for (x = is_symbol(car(sc->code)) ? cadr(sc->code) : car(sc->code), y = sc->args; y != sc->NIL; x = cdr(x), y = cdr(y)) { new_slot_in_env(sc, caar(x), car(y)); } if (is_symbol(car(sc->code))) { /* named let */ for (x = cadr(sc->code), sc->args = sc->NIL; x != sc->NIL; x = cdr(x)) { if (!is_pair(x)) Error_1(sc, "Bad syntax of binding in let", x); if (!is_list(sc, car(x))) Error_1(sc, "Bad syntax of binding in let", car(x)); gc_disable(sc, 1); sc->args = cons(sc, caar(x), sc->args); gc_enable(sc); } gc_disable(sc, 2 + gc_reservations (new_slot_in_env)); x = mk_closure(sc, cons(sc, reverse_in_place(sc, sc->NIL, sc->args), cddr(sc->code)), sc->envir); new_slot_in_env(sc, car(sc->code), x); gc_enable(sc); sc->code = cddr(sc->code); sc->args = sc->NIL; } else { sc->code = cdr(sc->code); sc->args = sc->NIL; } s_thread_to(sc,OP_BEGIN); CASE(OP_LET0AST): /* let* */ if (car(sc->code) == sc->NIL) { new_frame_in_env(sc, sc->envir); sc->code = cdr(sc->code); s_thread_to(sc,OP_BEGIN); } if(!is_pair(car(sc->code)) || !is_pair(caar(sc->code)) || !is_pair(cdaar(sc->code))) { Error_1(sc, "Bad syntax of binding spec in let*", car(sc->code)); } s_save(sc,OP_LET1AST, cdr(sc->code), car(sc->code)); sc->code = cadaar(sc->code); s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); CASE(OP_LET1AST): /* let* (make new frame) */ new_frame_in_env(sc, sc->envir); s_thread_to(sc,OP_LET2AST); CASE(OP_LET2AST): /* let* (calculate parameters) */ new_slot_in_env(sc, caar(sc->code), sc->value); sc->code = cdr(sc->code); if (is_pair(sc->code)) { /* continue */ s_save(sc,OP_LET2AST, sc->args, sc->code); sc->code = cadar(sc->code); sc->args = sc->NIL; s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); } else { /* end */ sc->code = sc->args; sc->args = sc->NIL; s_thread_to(sc,OP_BEGIN); } CASE(OP_LET0REC): /* letrec */ new_frame_in_env(sc, sc->envir); sc->args = sc->NIL; sc->value = sc->code; sc->code = car(sc->code); s_thread_to(sc,OP_LET1REC); CASE(OP_LET1REC): /* letrec (calculate parameters) */ gc_disable(sc, 1); sc->args = cons(sc, sc->value, sc->args); gc_enable(sc); if (is_pair(sc->code)) { /* continue */ if (!is_pair(car(sc->code)) || !is_pair(cdar(sc->code))) { Error_1(sc, "Bad syntax of binding spec in letrec", car(sc->code)); } s_save(sc,OP_LET1REC, sc->args, cdr(sc->code)); sc->code = cadar(sc->code); sc->args = sc->NIL; s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); } else { /* end */ sc->args = reverse_in_place(sc, sc->NIL, sc->args); sc->code = car(sc->args); sc->args = cdr(sc->args); s_thread_to(sc,OP_LET2REC); } CASE(OP_LET2REC): /* letrec */ for (x = car(sc->code), y = sc->args; y != sc->NIL; x = cdr(x), y = cdr(y)) { new_slot_in_env(sc, caar(x), car(y)); } sc->code = cdr(sc->code); sc->args = sc->NIL; s_thread_to(sc,OP_BEGIN); CASE(OP_COND0): /* cond */ if (!is_pair(sc->code)) { Error_0(sc,"syntax error in cond"); } s_save(sc,OP_COND1, sc->NIL, sc->code); sc->code = caar(sc->code); s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); CASE(OP_COND1): /* cond */ if (is_true(sc->value)) { if ((sc->code = cdar(sc->code)) == sc->NIL) { s_return(sc,sc->value); } if(!sc->code || car(sc->code)==sc->FEED_TO) { if(!is_pair(cdr(sc->code))) { Error_0(sc,"syntax error in cond"); } gc_disable(sc, 4); x=cons(sc, sc->QUOTE, cons(sc, sc->value, sc->NIL)); sc->code=cons(sc,cadr(sc->code),cons(sc,x,sc->NIL)); gc_enable(sc); s_thread_to(sc,OP_EVAL); } s_thread_to(sc,OP_BEGIN); } else { if ((sc->code = cdr(sc->code)) == sc->NIL) { s_return(sc,sc->NIL); } else { s_save(sc,OP_COND1, sc->NIL, sc->code); sc->code = caar(sc->code); s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); } } CASE(OP_DELAY): /* delay */ gc_disable(sc, 2); x = mk_closure(sc, cons(sc, sc->NIL, sc->code), sc->envir); typeflag(x)=T_PROMISE; s_return_enable_gc(sc,x); CASE(OP_AND0): /* and */ if (sc->code == sc->NIL) { s_return(sc,sc->T); } s_save(sc,OP_AND1, sc->NIL, cdr(sc->code)); if (cdr(sc->code) != sc->NIL) s_clear_flag(sc, TAIL_CONTEXT); sc->code = car(sc->code); s_thread_to(sc,OP_EVAL); CASE(OP_AND1): /* and */ if (is_false(sc->value)) { s_return(sc,sc->value); } else if (sc->code == sc->NIL) { s_return(sc,sc->value); } else { s_save(sc,OP_AND1, sc->NIL, cdr(sc->code)); if (cdr(sc->code) != sc->NIL) s_clear_flag(sc, TAIL_CONTEXT); sc->code = car(sc->code); s_thread_to(sc,OP_EVAL); } CASE(OP_OR0): /* or */ if (sc->code == sc->NIL) { s_return(sc,sc->F); } s_save(sc,OP_OR1, sc->NIL, cdr(sc->code)); if (cdr(sc->code) != sc->NIL) s_clear_flag(sc, TAIL_CONTEXT); sc->code = car(sc->code); s_thread_to(sc,OP_EVAL); CASE(OP_OR1): /* or */ if (is_true(sc->value)) { s_return(sc,sc->value); } else if (sc->code == sc->NIL) { s_return(sc,sc->value); } else { s_save(sc,OP_OR1, sc->NIL, cdr(sc->code)); if (cdr(sc->code) != sc->NIL) s_clear_flag(sc, TAIL_CONTEXT); sc->code = car(sc->code); s_thread_to(sc,OP_EVAL); } CASE(OP_C0STREAM): /* cons-stream */ s_save(sc,OP_C1STREAM, sc->NIL, cdr(sc->code)); sc->code = car(sc->code); s_thread_to(sc,OP_EVAL); CASE(OP_C1STREAM): /* cons-stream */ sc->args = sc->value; /* save sc->value to register sc->args for gc */ gc_disable(sc, 3); x = mk_closure(sc, cons(sc, sc->NIL, sc->code), sc->envir); typeflag(x)=T_PROMISE; s_return_enable_gc(sc, cons(sc, sc->args, x)); CASE(OP_MACRO0): /* macro */ if (is_pair(car(sc->code))) { x = caar(sc->code); gc_disable(sc, 2); sc->code = cons(sc, sc->LAMBDA, cons(sc, cdar(sc->code), cdr(sc->code))); gc_enable(sc); } else { x = car(sc->code); sc->code = cadr(sc->code); } if (!is_symbol(x)) { Error_0(sc,"variable is not a symbol"); } s_save(sc,OP_MACRO1, sc->NIL, x); s_thread_to(sc,OP_EVAL); CASE(OP_MACRO1): { /* macro */ pointer *sslot; typeflag(sc->value) = T_MACRO; x = find_slot_spec_in_env(sc, sc->envir, sc->code, 0, &sslot); if (x != sc->NIL) { set_slot_in_env(sc, x, sc->value); } else { new_slot_spec_in_env(sc, sc->code, sc->value, sslot); } s_return(sc,sc->code); } CASE(OP_CASE0): /* case */ s_save(sc,OP_CASE1, sc->NIL, cdr(sc->code)); sc->code = car(sc->code); s_clear_flag(sc, TAIL_CONTEXT); s_thread_to(sc,OP_EVAL); CASE(OP_CASE1): /* case */ for (x = sc->code; x != sc->NIL; x = cdr(x)) { if (!is_pair(y = caar(x))) { break; } for ( ; y != sc->NIL; y = cdr(y)) { if (eqv(car(y), sc->value)) { break; } } if (y != sc->NIL) { break; } } if (x != sc->NIL) { if (is_pair(caar(x))) { sc->code = cdar(x); s_thread_to(sc,OP_BEGIN); } else {/* else */ s_save(sc,OP_CASE2, sc->NIL, cdar(x)); sc->code = caar(x); s_thread_to(sc,OP_EVAL); } } else { s_return(sc,sc->NIL); } CASE(OP_CASE2): /* case */ if (is_true(sc->value)) { s_thread_to(sc,OP_BEGIN); } else { s_return(sc,sc->NIL); } CASE(OP_PAPPLY): /* apply */ sc->code = car(sc->args); sc->args = list_star(sc,cdr(sc->args)); /*sc->args = cadr(sc->args);*/ s_thread_to(sc,OP_APPLY); CASE(OP_PEVAL): /* eval */ if(cdr(sc->args)!=sc->NIL) { sc->envir=cadr(sc->args); } sc->code = car(sc->args); s_thread_to(sc,OP_EVAL); CASE(OP_CONTINUATION): /* call-with-current-continuation */ sc->code = car(sc->args); gc_disable(sc, 2); sc->args = cons(sc, mk_continuation(sc, sc->dump), sc->NIL); gc_enable(sc); s_thread_to(sc,OP_APPLY); #if USE_MATH CASE(OP_INEX2EX): /* inexact->exact */ x=car(sc->args); if(num_is_integer(x)) { s_return(sc,x); } else if(modf(rvalue_unchecked(x),&dd)==0.0) { s_return(sc,mk_integer(sc,ivalue(x))); } else { Error_1(sc, "inexact->exact: not integral", x); } CASE(OP_EXP): x=car(sc->args); s_return(sc, mk_real(sc, exp(rvalue(x)))); CASE(OP_LOG): x=car(sc->args); s_return(sc, mk_real(sc, log(rvalue(x)))); CASE(OP_SIN): x=car(sc->args); s_return(sc, mk_real(sc, sin(rvalue(x)))); CASE(OP_COS): x=car(sc->args); s_return(sc, mk_real(sc, cos(rvalue(x)))); CASE(OP_TAN): x=car(sc->args); s_return(sc, mk_real(sc, tan(rvalue(x)))); CASE(OP_ASIN): x=car(sc->args); s_return(sc, mk_real(sc, asin(rvalue(x)))); CASE(OP_ACOS): x=car(sc->args); s_return(sc, mk_real(sc, acos(rvalue(x)))); CASE(OP_ATAN): x=car(sc->args); if(cdr(sc->args)==sc->NIL) { s_return(sc, mk_real(sc, atan(rvalue(x)))); } else { pointer y=cadr(sc->args); s_return(sc, mk_real(sc, atan2(rvalue(x),rvalue(y)))); } CASE(OP_SQRT): x=car(sc->args); s_return(sc, mk_real(sc, sqrt(rvalue(x)))); CASE(OP_EXPT): { double result; int real_result=1; pointer y=cadr(sc->args); x=car(sc->args); if (num_is_integer(x) && num_is_integer(y)) real_result=0; /* This 'if' is an R5RS compatibility fix. */ /* NOTE: Remove this 'if' fix for R6RS. */ if (rvalue(x) == 0 && rvalue(y) < 0) { result = 0.0; } else { result = pow(rvalue(x),rvalue(y)); } /* Before returning integer result make sure we can. */ /* If the test fails, result is too big for integer. */ if (!real_result) { long result_as_long = (long)result; if (result != (double)result_as_long) real_result = 1; } if (real_result) { s_return(sc, mk_real(sc, result)); } else { s_return(sc, mk_integer(sc, result)); } } CASE(OP_FLOOR): x=car(sc->args); s_return(sc, mk_real(sc, floor(rvalue(x)))); CASE(OP_CEILING): x=car(sc->args); s_return(sc, mk_real(sc, ceil(rvalue(x)))); CASE(OP_TRUNCATE ): { double rvalue_of_x ; x=car(sc->args); rvalue_of_x = rvalue(x) ; if (rvalue_of_x > 0) { s_return(sc, mk_real(sc, floor(rvalue_of_x))); } else { s_return(sc, mk_real(sc, ceil(rvalue_of_x))); } } CASE(OP_ROUND): x=car(sc->args); if (num_is_integer(x)) s_return(sc, x); s_return(sc, mk_real(sc, round_per_R5RS(rvalue(x)))); #endif CASE(OP_ADD): /* + */ v=num_zero; for (x = sc->args; x != sc->NIL; x = cdr(x)) { v=num_add(v,nvalue(car(x))); } gc_disable(sc, 1); s_return_enable_gc(sc, mk_number(sc, v)); CASE(OP_MUL): /* * */ v=num_one; for (x = sc->args; x != sc->NIL; x = cdr(x)) { v=num_mul(v,nvalue(car(x))); } gc_disable(sc, 1); s_return_enable_gc(sc, mk_number(sc, v)); CASE(OP_SUB): /* - */ if(cdr(sc->args)==sc->NIL) { x=sc->args; v=num_zero; } else { x = cdr(sc->args); v = nvalue(car(sc->args)); } for (; x != sc->NIL; x = cdr(x)) { v=num_sub(v,nvalue(car(x))); } gc_disable(sc, 1); s_return_enable_gc(sc, mk_number(sc, v)); CASE(OP_DIV): /* / */ if(cdr(sc->args)==sc->NIL) { x=sc->args; v=num_one; } else { x = cdr(sc->args); v = nvalue(car(sc->args)); } for (; x != sc->NIL; x = cdr(x)) { if (!is_zero_double(rvalue(car(x)))) v=num_div(v,nvalue(car(x))); else { Error_0(sc,"/: division by zero"); } } gc_disable(sc, 1); s_return_enable_gc(sc, mk_number(sc, v)); CASE(OP_INTDIV): /* quotient */ if(cdr(sc->args)==sc->NIL) { x=sc->args; v=num_one; } else { x = cdr(sc->args); v = nvalue(car(sc->args)); } for (; x != sc->NIL; x = cdr(x)) { if (ivalue(car(x)) != 0) v=num_intdiv(v,nvalue(car(x))); else { Error_0(sc,"quotient: division by zero"); } } gc_disable(sc, 1); s_return_enable_gc(sc, mk_number(sc, v)); CASE(OP_REM): /* remainder */ v = nvalue(car(sc->args)); if (ivalue(cadr(sc->args)) != 0) v=num_rem(v,nvalue(cadr(sc->args))); else { Error_0(sc,"remainder: division by zero"); } gc_disable(sc, 1); s_return_enable_gc(sc, mk_number(sc, v)); CASE(OP_MOD): /* modulo */ v = nvalue(car(sc->args)); if (ivalue(cadr(sc->args)) != 0) v=num_mod(v,nvalue(cadr(sc->args))); else { Error_0(sc,"modulo: division by zero"); } gc_disable(sc, 1); s_return_enable_gc(sc, mk_number(sc, v)); CASE(OP_CAR): /* car */ s_return(sc,caar(sc->args)); CASE(OP_CDR): /* cdr */ s_return(sc,cdar(sc->args)); CASE(OP_CONS): /* cons */ cdr(sc->args) = cadr(sc->args); s_return(sc,sc->args); CASE(OP_SETCAR): /* set-car! */ if(!is_immutable(car(sc->args))) { caar(sc->args) = cadr(sc->args); s_return(sc,car(sc->args)); } else { Error_0(sc,"set-car!: unable to alter immutable pair"); } CASE(OP_SETCDR): /* set-cdr! */ if(!is_immutable(car(sc->args))) { cdar(sc->args) = cadr(sc->args); s_return(sc,car(sc->args)); } else { Error_0(sc,"set-cdr!: unable to alter immutable pair"); } CASE(OP_CHAR2INT): { /* char->integer */ char c; c=(char)ivalue(car(sc->args)); gc_disable(sc, 1); s_return_enable_gc(sc, mk_integer(sc, (unsigned char) c)); } CASE(OP_INT2CHAR): { /* integer->char */ unsigned char c; c=(unsigned char)ivalue(car(sc->args)); gc_disable(sc, 1); s_return_enable_gc(sc, mk_character(sc, (char) c)); } CASE(OP_CHARUPCASE): { unsigned char c; c=(unsigned char)ivalue(car(sc->args)); c=toupper(c); gc_disable(sc, 1); s_return_enable_gc(sc, mk_character(sc, (char) c)); } CASE(OP_CHARDNCASE): { unsigned char c; c=(unsigned char)ivalue(car(sc->args)); c=tolower(c); gc_disable(sc, 1); s_return_enable_gc(sc, mk_character(sc, (char) c)); } CASE(OP_STR2SYM): /* string->symbol */ gc_disable(sc, gc_reservations (mk_symbol)); s_return_enable_gc(sc, mk_symbol(sc, strvalue(car(sc->args)))); CASE(OP_STR2ATOM): /* string->atom */ { char *s=strvalue(car(sc->args)); long pf = 0; if(cdr(sc->args)!=sc->NIL) { /* we know cadr(sc->args) is a natural number */ /* see if it is 2, 8, 10, or 16, or error */ pf = ivalue_unchecked(cadr(sc->args)); if(pf == 16 || pf == 10 || pf == 8 || pf == 2) { /* base is OK */ } else { pf = -1; } } if (pf < 0) { Error_1(sc, "string->atom: bad base", cadr(sc->args)); } else if(*s=='#') /* no use of base! */ { s_return(sc, mk_sharp_const(sc, s+1)); } else { if (pf == 0 || pf == 10) { s_return(sc, mk_atom(sc, s)); } else { char *ep; long iv = strtol(s,&ep,(int )pf); if (*ep == 0) { s_return(sc, mk_integer(sc, iv)); } else { s_return(sc, sc->F); } } } } CASE(OP_SYM2STR): /* symbol->string */ gc_disable(sc, 1); x=mk_string(sc,symname(car(sc->args))); setimmutable(x); s_return_enable_gc(sc, x); CASE(OP_ATOM2STR): /* atom->string */ { long pf = 0; x=car(sc->args); if(cdr(sc->args)!=sc->NIL) { /* we know cadr(sc->args) is a natural number */ /* see if it is 2, 8, 10, or 16, or error */ pf = ivalue_unchecked(cadr(sc->args)); if(is_number(x) && (pf == 16 || pf == 10 || pf == 8 || pf == 2)) { /* base is OK */ } else { pf = -1; } } if (pf < 0) { Error_1(sc, "atom->string: bad base", cadr(sc->args)); } else if(is_number(x) || is_character(x) || is_string(x) || is_symbol(x)) { char *p; int len; atom2str(sc,x,(int )pf,&p,&len); gc_disable(sc, 1); s_return_enable_gc(sc, mk_counted_string(sc, p, len)); } else { Error_1(sc, "atom->string: not an atom", x); } } CASE(OP_MKSTRING): { /* make-string */ int fill=' '; int len; len=ivalue(car(sc->args)); if(cdr(sc->args)!=sc->NIL) { fill=charvalue(cadr(sc->args)); } gc_disable(sc, 1); s_return_enable_gc(sc, mk_empty_string(sc, len, (char) fill)); } CASE(OP_STRLEN): /* string-length */ gc_disable(sc, 1); s_return_enable_gc(sc, mk_integer(sc, strlength(car(sc->args)))); CASE(OP_STRREF): { /* string-ref */ char *str; int index; str=strvalue(car(sc->args)); index=ivalue(cadr(sc->args)); if(index>=strlength(car(sc->args))) { Error_1(sc, "string-ref: out of bounds", cadr(sc->args)); } gc_disable(sc, 1); s_return_enable_gc(sc, mk_character(sc, ((unsigned char*) str)[index])); } CASE(OP_STRSET): { /* string-set! */ char *str; int index; int c; if(is_immutable(car(sc->args))) { Error_1(sc, "string-set!: unable to alter immutable string", car(sc->args)); } str=strvalue(car(sc->args)); index=ivalue(cadr(sc->args)); if(index>=strlength(car(sc->args))) { Error_1(sc, "string-set!: out of bounds", cadr(sc->args)); } c=charvalue(caddr(sc->args)); str[index]=(char)c; s_return(sc,car(sc->args)); } CASE(OP_STRAPPEND): { /* string-append */ /* in 1.29 string-append was in Scheme in init.scm but was too slow */ int len = 0; pointer newstr; char *pos; /* compute needed length for new string */ for (x = sc->args; x != sc->NIL; x = cdr(x)) { len += strlength(car(x)); } gc_disable(sc, 1); newstr = mk_empty_string(sc, len, ' '); /* store the contents of the argument strings into the new string */ for (pos = strvalue(newstr), x = sc->args; x != sc->NIL; pos += strlength(car(x)), x = cdr(x)) { memcpy(pos, strvalue(car(x)), strlength(car(x))); } s_return_enable_gc(sc, newstr); } CASE(OP_SUBSTR): { /* substring */ char *str; int index0; int index1; str=strvalue(car(sc->args)); index0=ivalue(cadr(sc->args)); if(index0>strlength(car(sc->args))) { Error_1(sc, "substring: start out of bounds", cadr(sc->args)); } if(cddr(sc->args)!=sc->NIL) { index1=ivalue(caddr(sc->args)); if(index1>strlength(car(sc->args)) || index1args)); } } else { index1=strlength(car(sc->args)); } gc_disable(sc, 1); s_return_enable_gc(sc, mk_counted_string(sc, str + index0, index1 - index0)); } CASE(OP_VECTOR): { /* vector */ int i; pointer vec; int len=list_length(sc,sc->args); if(len<0) { Error_1(sc, "vector: not a proper list", sc->args); } vec=mk_vector(sc,len); if(sc->no_memory) { s_return(sc, sc->sink); } for (x = sc->args, i = 0; is_pair(x); x = cdr(x), i++) { set_vector_elem(vec,i,car(x)); } s_return(sc,vec); } CASE(OP_MKVECTOR): { /* make-vector */ pointer fill=sc->NIL; int len; pointer vec; len=ivalue(car(sc->args)); if(cdr(sc->args)!=sc->NIL) { fill=cadr(sc->args); } vec=mk_vector(sc,len); if(sc->no_memory) { s_return(sc, sc->sink); } if(fill!=sc->NIL) { fill_vector(vec,fill); } s_return(sc,vec); } CASE(OP_VECLEN): /* vector-length */ gc_disable(sc, 1); s_return_enable_gc(sc, mk_integer(sc, vector_length(car(sc->args)))); CASE(OP_VECREF): { /* vector-ref */ int index; index=ivalue(cadr(sc->args)); if(index >= vector_length(car(sc->args))) { Error_1(sc, "vector-ref: out of bounds", cadr(sc->args)); } s_return(sc,vector_elem(car(sc->args),index)); } CASE(OP_VECSET): { /* vector-set! */ int index; if(is_immutable(car(sc->args))) { Error_1(sc, "vector-set!: unable to alter immutable vector", car(sc->args)); } index=ivalue(cadr(sc->args)); if(index >= vector_length(car(sc->args))) { Error_1(sc, "vector-set!: out of bounds", cadr(sc->args)); } set_vector_elem(car(sc->args),index,caddr(sc->args)); s_return(sc,car(sc->args)); } CASE(OP_NOT): /* not */ s_retbool(is_false(car(sc->args))); CASE(OP_BOOLP): /* boolean? */ s_retbool(car(sc->args) == sc->F || car(sc->args) == sc->T); CASE(OP_EOFOBJP): /* boolean? */ s_retbool(car(sc->args) == sc->EOF_OBJ); CASE(OP_NULLP): /* null? */ s_retbool(car(sc->args) == sc->NIL); CASE(OP_NUMEQ): /* = */ CASE(OP_LESS): /* < */ CASE(OP_GRE): /* > */ CASE(OP_LEQ): /* <= */ CASE(OP_GEQ): /* >= */ switch(op) { case OP_NUMEQ: comp_func=num_eq; break; case OP_LESS: comp_func=num_lt; break; case OP_GRE: comp_func=num_gt; break; case OP_LEQ: comp_func=num_le; break; case OP_GEQ: comp_func=num_ge; break; default: assert (! "reached"); } x=sc->args; v=nvalue(car(x)); x=cdr(x); for (; x != sc->NIL; x = cdr(x)) { if(!comp_func(v,nvalue(car(x)))) { s_retbool(0); } v=nvalue(car(x)); } s_retbool(1); CASE(OP_SYMBOLP): /* symbol? */ s_retbool(is_symbol(car(sc->args))); CASE(OP_NUMBERP): /* number? */ s_retbool(is_number(car(sc->args))); CASE(OP_STRINGP): /* string? */ s_retbool(is_string(car(sc->args))); CASE(OP_INTEGERP): /* integer? */ s_retbool(is_integer(car(sc->args))); CASE(OP_REALP): /* real? */ s_retbool(is_number(car(sc->args))); /* All numbers are real */ CASE(OP_CHARP): /* char? */ s_retbool(is_character(car(sc->args))); #if USE_CHAR_CLASSIFIERS CASE(OP_CHARAP): /* char-alphabetic? */ s_retbool(Cisalpha(ivalue(car(sc->args)))); CASE(OP_CHARNP): /* char-numeric? */ s_retbool(Cisdigit(ivalue(car(sc->args)))); CASE(OP_CHARWP): /* char-whitespace? */ s_retbool(Cisspace(ivalue(car(sc->args)))); CASE(OP_CHARUP): /* char-upper-case? */ s_retbool(Cisupper(ivalue(car(sc->args)))); CASE(OP_CHARLP): /* char-lower-case? */ s_retbool(Cislower(ivalue(car(sc->args)))); #endif CASE(OP_PORTP): /* port? */ s_retbool(is_port(car(sc->args))); CASE(OP_INPORTP): /* input-port? */ s_retbool(is_inport(car(sc->args))); CASE(OP_OUTPORTP): /* output-port? */ s_retbool(is_outport(car(sc->args))); CASE(OP_PROCP): /* procedure? */ /*-- * continuation should be procedure by the example * (call-with-current-continuation procedure?) ==> #t * in R^3 report sec. 6.9 */ s_retbool(is_proc(car(sc->args)) || is_closure(car(sc->args)) || is_continuation(car(sc->args)) || is_foreign(car(sc->args))); CASE(OP_PAIRP): /* pair? */ s_retbool(is_pair(car(sc->args))); CASE(OP_LISTP): /* list? */ s_retbool(list_length(sc,car(sc->args)) >= 0); CASE(OP_ENVP): /* environment? */ s_retbool(is_environment(car(sc->args))); CASE(OP_VECTORP): /* vector? */ s_retbool(is_vector(car(sc->args))); CASE(OP_EQ): /* eq? */ s_retbool(car(sc->args) == cadr(sc->args)); CASE(OP_EQV): /* eqv? */ s_retbool(eqv(car(sc->args), cadr(sc->args))); CASE(OP_FORCE): /* force */ sc->code = car(sc->args); if (is_promise(sc->code)) { /* Should change type to closure here */ s_save(sc, OP_SAVE_FORCED, sc->NIL, sc->code); sc->args = sc->NIL; s_thread_to(sc,OP_APPLY); } else { s_return(sc,sc->code); } CASE(OP_SAVE_FORCED): /* Save forced value replacing promise */ copy_value(sc, sc->code, sc->value); s_return(sc,sc->value); CASE(OP_WRITE): /* write */ CASE(OP_DISPLAY): /* display */ CASE(OP_WRITE_CHAR): /* write-char */ if(is_pair(cdr(sc->args))) { if(cadr(sc->args)!=sc->outport) { x=cons(sc,sc->outport,sc->NIL); s_save(sc,OP_SET_OUTPORT, x, sc->NIL); sc->outport=cadr(sc->args); } } sc->args = car(sc->args); if(op==OP_WRITE) { sc->print_flag = 1; } else { sc->print_flag = 0; } s_thread_to(sc,OP_P0LIST); CASE(OP_NEWLINE): /* newline */ if(is_pair(sc->args)) { if(car(sc->args)!=sc->outport) { x=cons(sc,sc->outport,sc->NIL); s_save(sc,OP_SET_OUTPORT, x, sc->NIL); sc->outport=car(sc->args); } } putstr(sc, "\n"); s_return(sc,sc->T); CASE(OP_ERR0): /* error */ sc->retcode=-1; if (!is_string(car(sc->args))) { sc->args=cons(sc,mk_string(sc," -- "),sc->args); setimmutable(car(sc->args)); } putstr(sc, "Error: "); putstr(sc, strvalue(car(sc->args))); sc->args = cdr(sc->args); s_thread_to(sc,OP_ERR1); CASE(OP_ERR1): /* error */ putstr(sc, " "); if (sc->args != sc->NIL) { s_save(sc,OP_ERR1, cdr(sc->args), sc->NIL); sc->args = car(sc->args); sc->print_flag = 1; s_thread_to(sc,OP_P0LIST); } else { putstr(sc, "\n"); if(sc->interactive_repl) { s_thread_to(sc,OP_T0LVL); } else { return; } } CASE(OP_REVERSE): /* reverse */ s_return(sc,reverse(sc, sc->NIL, car(sc->args))); CASE(OP_REVERSE_IN_PLACE): /* reverse! */ s_return(sc, reverse_in_place(sc, sc->NIL, car(sc->args))); CASE(OP_LIST_STAR): /* list* */ s_return(sc,list_star(sc,sc->args)); CASE(OP_APPEND): /* append */ x = sc->NIL; y = sc->args; if (y == x) { s_return(sc, x); } /* cdr() in the while condition is not a typo. If car() */ /* is used (append '() 'a) will return the wrong result.*/ while (cdr(y) != sc->NIL) { x = revappend(sc, x, car(y)); y = cdr(y); if (x == sc->F) { Error_0(sc, "non-list argument to append"); } } s_return(sc, reverse_in_place(sc, car(y), x)); #if USE_PLIST CASE(OP_SET_SYMBOL_PROPERTY): /* set-symbol-property! */ gc_disable(sc, gc_reservations(set_property)); s_return_enable_gc(sc, set_property(sc, car(sc->args), cadr(sc->args), caddr(sc->args))); CASE(OP_SYMBOL_PROPERTY): /* symbol-property */ s_return(sc, get_property(sc, car(sc->args), cadr(sc->args))); #endif /* USE_PLIST */ CASE(OP_TAG_VALUE): { /* not exposed */ /* This tags sc->value with car(sc->args). Useful to tag * results of opcode evaluations. */ pointer a, b, c; free_cons(sc, sc->args, &a, &b); free_cons(sc, b, &b, &c); assert(c == sc->NIL); s_return(sc, mk_tagged_value(sc, sc->value, a, b)); } CASE(OP_MK_TAGGED): /* make-tagged-value */ if (is_vector(car(sc->args))) Error_0(sc, "cannot tag vector"); s_return(sc, mk_tagged_value(sc, car(sc->args), car(cadr(sc->args)), cdr(cadr(sc->args)))); CASE(OP_GET_TAG): /* get-tag */ s_return(sc, get_tag(sc, car(sc->args))); CASE(OP_QUIT): /* quit */ if(is_pair(sc->args)) { sc->retcode=ivalue(car(sc->args)); } return; CASE(OP_GC): /* gc */ gc(sc, sc->NIL, sc->NIL); s_return(sc,sc->T); CASE(OP_GCVERB): /* gc-verbose */ { int was = sc->gc_verbose; sc->gc_verbose = (car(sc->args) != sc->F); s_retbool(was); } CASE(OP_NEWSEGMENT): /* new-segment */ if (!is_pair(sc->args) || !is_number(car(sc->args))) { Error_0(sc,"new-segment: argument must be a number"); } alloc_cellseg(sc, (int) ivalue(car(sc->args))); s_return(sc,sc->T); CASE(OP_OBLIST): /* oblist */ s_return(sc, oblist_all_symbols(sc)); CASE(OP_CURR_INPORT): /* current-input-port */ s_return(sc,sc->inport); CASE(OP_CURR_OUTPORT): /* current-output-port */ s_return(sc,sc->outport); CASE(OP_OPEN_INFILE): /* open-input-file */ CASE(OP_OPEN_OUTFILE): /* open-output-file */ CASE(OP_OPEN_INOUTFILE): /* open-input-output-file */ { int prop=0; pointer p; switch(op) { case OP_OPEN_INFILE: prop=port_input; break; case OP_OPEN_OUTFILE: prop=port_output; break; case OP_OPEN_INOUTFILE: prop=port_input|port_output; break; default: assert (! "reached"); } p=port_from_filename(sc,strvalue(car(sc->args)),prop); if(p==sc->NIL) { s_return(sc,sc->F); } s_return(sc,p); break; } #if USE_STRING_PORTS CASE(OP_OPEN_INSTRING): /* open-input-string */ CASE(OP_OPEN_INOUTSTRING): /* open-input-output-string */ { int prop=0; pointer p; switch(op) { case OP_OPEN_INSTRING: prop=port_input; break; case OP_OPEN_INOUTSTRING: prop=port_input|port_output; break; default: assert (! "reached"); } p=port_from_string(sc, strvalue(car(sc->args)), strvalue(car(sc->args))+strlength(car(sc->args)), prop); if(p==sc->NIL) { s_return(sc,sc->F); } s_return(sc,p); } CASE(OP_OPEN_OUTSTRING): /* open-output-string */ { pointer p; if(car(sc->args)==sc->NIL) { p=port_from_scratch(sc); if(p==sc->NIL) { s_return(sc,sc->F); } } else { p=port_from_string(sc, strvalue(car(sc->args)), strvalue(car(sc->args))+strlength(car(sc->args)), port_output); if(p==sc->NIL) { s_return(sc,sc->F); } } s_return(sc,p); } CASE(OP_GET_OUTSTRING): /* get-output-string */ { port *p; if ((p=car(sc->args)->_object._port)->kind&port_string) { gc_disable(sc, 1); s_return_enable_gc( sc, mk_counted_string(sc, p->rep.string.start, p->rep.string.curr - p->rep.string.start)); } s_return(sc,sc->F); } #endif CASE(OP_CLOSE_INPORT): /* close-input-port */ port_close(sc,car(sc->args),port_input); s_return(sc,sc->T); CASE(OP_CLOSE_OUTPORT): /* close-output-port */ port_close(sc,car(sc->args),port_output); s_return(sc,sc->T); CASE(OP_INT_ENV): /* interaction-environment */ s_return(sc,sc->global_env); CASE(OP_CURR_ENV): /* current-environment */ s_return(sc,sc->envir); /* ========== reading part ========== */ CASE(OP_READ): if(!is_pair(sc->args)) { s_thread_to(sc,OP_READ_INTERNAL); } if(!is_inport(car(sc->args))) { Error_1(sc, "read: not an input port", car(sc->args)); } if(car(sc->args)==sc->inport) { s_thread_to(sc,OP_READ_INTERNAL); } x=sc->inport; sc->inport=car(sc->args); x=cons(sc,x,sc->NIL); s_save(sc,OP_SET_INPORT, x, sc->NIL); s_thread_to(sc,OP_READ_INTERNAL); CASE(OP_READ_CHAR): /* read-char */ CASE(OP_PEEK_CHAR): /* peek-char */ { int c; if(is_pair(sc->args)) { if(car(sc->args)!=sc->inport) { x=sc->inport; x=cons(sc,x,sc->NIL); s_save(sc,OP_SET_INPORT, x, sc->NIL); sc->inport=car(sc->args); } } c=inchar(sc); if(c==EOF) { s_return(sc,sc->EOF_OBJ); } if(op==OP_PEEK_CHAR) { backchar(sc,c); } s_return(sc,mk_character(sc,c)); } CASE(OP_CHAR_READY): /* char-ready? */ { pointer p=sc->inport; int res; if(is_pair(sc->args)) { p=car(sc->args); } res=p->_object._port->kind&port_string; s_retbool(res); } CASE(OP_SET_INPORT): /* set-input-port */ sc->inport=car(sc->args); s_return(sc,sc->value); CASE(OP_SET_OUTPORT): /* set-output-port */ sc->outport=car(sc->args); s_return(sc,sc->value); CASE(OP_RDSEXPR): switch (sc->tok) { case TOK_EOF: s_return(sc,sc->EOF_OBJ); /* NOTREACHED */ case TOK_VEC: s_save(sc,OP_RDVEC,sc->NIL,sc->NIL); /* fall through */ case TOK_LPAREN: sc->tok = token(sc); if (sc->tok == TOK_RPAREN) { s_return(sc,sc->NIL); } else if (sc->tok == TOK_DOT) { Error_0(sc,"syntax error: illegal dot expression"); } else { #if SHOW_ERROR_LINE pointer filename; pointer lineno; #endif sc->nesting_stack[sc->file_i]++; #if SHOW_ERROR_LINE filename = sc->load_stack[sc->file_i].filename; lineno = sc->load_stack[sc->file_i].curr_line; s_save(sc, OP_TAG_VALUE, cons(sc, filename, cons(sc, lineno, sc->NIL)), sc->NIL); #endif s_save(sc,OP_RDLIST, sc->NIL, sc->NIL); s_thread_to(sc,OP_RDSEXPR); } case TOK_QUOTE: s_save(sc,OP_RDQUOTE, sc->NIL, sc->NIL); sc->tok = token(sc); s_thread_to(sc,OP_RDSEXPR); case TOK_BQUOTE: sc->tok = token(sc); if(sc->tok==TOK_VEC) { s_save(sc,OP_RDQQUOTEVEC, sc->NIL, sc->NIL); sc->tok=TOK_LPAREN; s_thread_to(sc,OP_RDSEXPR); } else { s_save(sc,OP_RDQQUOTE, sc->NIL, sc->NIL); } s_thread_to(sc,OP_RDSEXPR); case TOK_COMMA: s_save(sc,OP_RDUNQUOTE, sc->NIL, sc->NIL); sc->tok = token(sc); s_thread_to(sc,OP_RDSEXPR); case TOK_ATMARK: s_save(sc,OP_RDUQTSP, sc->NIL, sc->NIL); sc->tok = token(sc); s_thread_to(sc,OP_RDSEXPR); case TOK_ATOM: s_return(sc,mk_atom(sc, readstr_upto(sc, DELIMITERS))); case TOK_DQUOTE: x=readstrexp(sc); if(x==sc->F) { Error_0(sc,"Error reading string"); } setimmutable(x); s_return(sc,x); case TOK_SHARP: { pointer f=find_slot_in_env(sc,sc->envir,sc->SHARP_HOOK,1); if(f==sc->NIL) { Error_0(sc,"undefined sharp expression"); } else { sc->code=cons(sc,slot_value_in_env(f),sc->NIL); s_thread_to(sc,OP_EVAL); } } case TOK_SHARP_CONST: if ((x = mk_sharp_const(sc, readstr_upto(sc, DELIMITERS))) == sc->NIL) { Error_0(sc,"undefined sharp expression"); } else { s_return(sc,x); } default: Error_0(sc,"syntax error: illegal token"); } break; CASE(OP_RDLIST): { gc_disable(sc, 1); sc->args = cons(sc, sc->value, sc->args); gc_enable(sc); sc->tok = token(sc); if (sc->tok == TOK_EOF) { s_return(sc,sc->EOF_OBJ); } else if (sc->tok == TOK_RPAREN) { int c = inchar(sc); if (c != '\n') backchar(sc,c); else port_increment_current_line(sc, &sc->load_stack[sc->file_i], 1); sc->nesting_stack[sc->file_i]--; s_return(sc,reverse_in_place(sc, sc->NIL, sc->args)); } else if (sc->tok == TOK_DOT) { s_save(sc,OP_RDDOT, sc->args, sc->NIL); sc->tok = token(sc); s_thread_to(sc,OP_RDSEXPR); } else { s_save(sc,OP_RDLIST, sc->args, sc->NIL);; s_thread_to(sc,OP_RDSEXPR); } } CASE(OP_RDDOT): if (token(sc) != TOK_RPAREN) { Error_0(sc,"syntax error: illegal dot expression"); } else { sc->nesting_stack[sc->file_i]--; s_return(sc,reverse_in_place(sc, sc->value, sc->args)); } CASE(OP_RDQUOTE): gc_disable(sc, 2); s_return_enable_gc(sc, cons(sc, sc->QUOTE, cons(sc, sc->value, sc->NIL))); CASE(OP_RDQQUOTE): gc_disable(sc, 2); s_return_enable_gc(sc, cons(sc, sc->QQUOTE, cons(sc, sc->value, sc->NIL))); CASE(OP_RDQQUOTEVEC): gc_disable(sc, 5 + 2 * gc_reservations (mk_symbol)); s_return_enable_gc(sc,cons(sc, mk_symbol(sc,"apply"), cons(sc, mk_symbol(sc,"vector"), cons(sc,cons(sc, sc->QQUOTE, cons(sc,sc->value,sc->NIL)), sc->NIL)))); CASE(OP_RDUNQUOTE): gc_disable(sc, 2); s_return_enable_gc(sc, cons(sc, sc->UNQUOTE, cons(sc, sc->value, sc->NIL))); CASE(OP_RDUQTSP): gc_disable(sc, 2); s_return_enable_gc(sc, cons(sc, sc->UNQUOTESP, cons(sc, sc->value, sc->NIL))); CASE(OP_RDVEC): /*sc->code=cons(sc,mk_proc(sc,OP_VECTOR),sc->value); s_thread_to(sc,OP_EVAL); Cannot be quoted*/ /*x=cons(sc,mk_proc(sc,OP_VECTOR),sc->value); s_return(sc,x); Cannot be part of pairs*/ /*sc->code=mk_proc(sc,OP_VECTOR); sc->args=sc->value; s_thread_to(sc,OP_APPLY);*/ sc->args=sc->value; s_thread_to(sc,OP_VECTOR); /* ========== printing part ========== */ CASE(OP_P0LIST): if(is_vector(sc->args)) { putstr(sc,"#("); sc->args=cons(sc,sc->args,mk_integer(sc,0)); s_thread_to(sc,OP_PVECFROM); } else if(is_environment(sc->args)) { putstr(sc,"#"); s_return(sc,sc->T); } else if (!is_pair(sc->args)) { printatom(sc, sc->args, sc->print_flag); s_return(sc,sc->T); } else if (car(sc->args) == sc->QUOTE && ok_abbrev(cdr(sc->args))) { putstr(sc, "'"); sc->args = cadr(sc->args); s_thread_to(sc,OP_P0LIST); } else if (car(sc->args) == sc->QQUOTE && ok_abbrev(cdr(sc->args))) { putstr(sc, "`"); sc->args = cadr(sc->args); s_thread_to(sc,OP_P0LIST); } else if (car(sc->args) == sc->UNQUOTE && ok_abbrev(cdr(sc->args))) { putstr(sc, ","); sc->args = cadr(sc->args); s_thread_to(sc,OP_P0LIST); } else if (car(sc->args) == sc->UNQUOTESP && ok_abbrev(cdr(sc->args))) { putstr(sc, ",@"); sc->args = cadr(sc->args); s_thread_to(sc,OP_P0LIST); } else { putstr(sc, "("); s_save(sc,OP_P1LIST, cdr(sc->args), sc->NIL); sc->args = car(sc->args); s_thread_to(sc,OP_P0LIST); } CASE(OP_P1LIST): if (is_pair(sc->args)) { s_save(sc,OP_P1LIST, cdr(sc->args), sc->NIL); putstr(sc, " "); sc->args = car(sc->args); s_thread_to(sc,OP_P0LIST); } else if(is_vector(sc->args)) { s_save(sc,OP_P1LIST,sc->NIL,sc->NIL); putstr(sc, " . "); s_thread_to(sc,OP_P0LIST); } else { if (sc->args != sc->NIL) { putstr(sc, " . "); printatom(sc, sc->args, sc->print_flag); } putstr(sc, ")"); s_return(sc,sc->T); } CASE(OP_PVECFROM): { int i=ivalue_unchecked(cdr(sc->args)); pointer vec=car(sc->args); int len = vector_length(vec); if(i==len) { putstr(sc,")"); s_return(sc,sc->T); } else { pointer elem=vector_elem(vec,i); cdr(sc->args) = mk_integer(sc, i + 1); s_save(sc,OP_PVECFROM, sc->args, sc->NIL); sc->args=elem; if (i > 0) putstr(sc," "); s_thread_to(sc,OP_P0LIST); } } CASE(OP_LIST_LENGTH): { /* length */ /* a.k */ long l = list_length(sc, car(sc->args)); if(l<0) { Error_1(sc, "length: not a list", car(sc->args)); } gc_disable(sc, 1); s_return_enable_gc(sc, mk_integer(sc, l)); } CASE(OP_ASSQ): /* assq */ /* a.k */ x = car(sc->args); for (y = cadr(sc->args); is_pair(y); y = cdr(y)) { if (!is_pair(car(y))) { Error_0(sc,"unable to handle non pair element"); } if (x == caar(y)) break; } if (is_pair(y)) { s_return(sc,car(y)); } else { s_return(sc,sc->F); } CASE(OP_GET_CLOSURE): /* get-closure-code */ /* a.k */ sc->args = car(sc->args); if (sc->args == sc->NIL) { s_return(sc,sc->F); } else if (is_closure(sc->args)) { gc_disable(sc, 1); s_return_enable_gc(sc, cons(sc, sc->LAMBDA, closure_code(sc->value))); } else if (is_macro(sc->args)) { gc_disable(sc, 1); s_return_enable_gc(sc, cons(sc, sc->LAMBDA, closure_code(sc->value))); } else { s_return(sc,sc->F); } CASE(OP_CLOSUREP): /* closure? */ /* * Note, macro object is also a closure. * Therefore, (closure? <#MACRO>) ==> #t */ s_retbool(is_closure(car(sc->args))); CASE(OP_MACROP): /* macro? */ s_retbool(is_macro(car(sc->args))); CASE(OP_VM_HISTORY): /* *vm-history* */ s_return(sc, history_flatten(sc)); default: snprintf(sc->strbuff,STRBUFFSIZE,"%d: illegal operator", op); Error_0(sc,sc->strbuff); } } } typedef int (*test_predicate)(pointer); static int is_any(pointer p) { (void)p; return 1; } static int is_nonneg(pointer p) { return ivalue(p)>=0 && is_integer(p); } /* Correspond carefully with following defines! */ static const struct { test_predicate fct; const char *kind; } tests[]={ {0,0}, /* unused */ {is_any, 0}, {is_string, "string"}, {is_symbol, "symbol"}, {is_port, "port"}, {is_inport,"input port"}, {is_outport,"output port"}, {is_environment, "environment"}, {is_pair, "pair"}, {0, "pair or '()"}, {is_character, "character"}, {is_vector, "vector"}, {is_number, "number"}, {is_integer, "integer"}, {is_nonneg, "non-negative integer"} }; #define TST_NONE 0 #define TST_ANY "\001" #define TST_STRING "\002" #define TST_SYMBOL "\003" #define TST_PORT "\004" #define TST_INPORT "\005" #define TST_OUTPORT "\006" #define TST_ENVIRONMENT "\007" #define TST_PAIR "\010" #define TST_LIST "\011" #define TST_CHAR "\012" #define TST_VECTOR "\013" #define TST_NUMBER "\014" #define TST_INTEGER "\015" #define TST_NATURAL "\016" #define INF_ARG 0xff static const struct op_code_info dispatch_table[]= { #define _OP_DEF(A,B,C,D,OP) {{A},B,C,{D}}, #include "opdefines.h" #undef _OP_DEF {{0},0,0,{0}}, }; static const char *procname(pointer x) { int n=procnum(x); const char *name=dispatch_table[n].name; if (name[0] == 0) { name="ILLEGAL!"; } return name; } static int check_arguments (scheme *sc, const struct op_code_info *pcd, char *msg, size_t msg_size) { int ok = 1; int n = list_length(sc, sc->args); /* Check number of arguments */ if (n < pcd->min_arity) { ok = 0; snprintf(msg, msg_size, "%s: needs%s %d argument(s)", pcd->name, pcd->min_arity == pcd->max_arity ? "" : " at least", pcd->min_arity); } if (ok && n>pcd->max_arity) { ok = 0; snprintf(msg, msg_size, "%s: needs%s %d argument(s)", pcd->name, pcd->min_arity == pcd->max_arity ? "" : " at most", pcd->max_arity); } if (ok) { if (pcd->arg_tests_encoding[0] != 0) { int i = 0; int j; const char *t = pcd->arg_tests_encoding; pointer arglist = sc->args; do { pointer arg = car(arglist); j = (int)t[0]; if (j == TST_LIST[0]) { if (arg != sc->NIL && !is_pair(arg)) break; } else { if (!tests[j].fct(arg)) break; } if (t[1] != 0 && i < sizeof pcd->arg_tests_encoding) { /* last test is replicated as necessary */ t++; } arglist = cdr(arglist); i++; } while (i < n); if (i < n) { ok = 0; snprintf(msg, msg_size, "%s: argument %d must be: %s, got: %s", pcd->name, i + 1, tests[j].kind, type_to_string(type(car(arglist)))); } } } return ok; } /* ========== Initialization of internal keywords ========== */ /* Symbols representing syntax are tagged with (OP . '()). */ static void assign_syntax(scheme *sc, enum scheme_opcodes op, char *name) { pointer x, y; pointer *slot; x = oblist_find_by_name(sc, name, &slot); assert (x == sc->NIL); x = immutable_cons(sc, mk_string(sc, name), sc->NIL); typeflag(x) = T_SYMBOL | T_SYNTAX; setimmutable(car(x)); y = mk_tagged_value(sc, x, mk_integer(sc, op), sc->NIL); free_cell(sc, x); setimmutable(get_tag(sc, y)); *slot = immutable_cons(sc, y, *slot); } /* Returns the opcode for the syntax represented by P. */ static int syntaxnum(scheme *sc, pointer p) { int op = ivalue_unchecked(car(get_tag(sc, p))); assert (op < OP_MAXDEFINED); return op; } static void assign_proc(scheme *sc, enum scheme_opcodes op, const char *name) { pointer x, y; x = mk_symbol(sc, name); y = mk_proc(sc,op); new_slot_in_env(sc, x, y); } static pointer mk_proc(scheme *sc, enum scheme_opcodes op) { pointer y; y = get_cell(sc, sc->NIL, sc->NIL); typeflag(y) = (T_PROC | T_ATOM); ivalue_unchecked(y) = (long) op; set_num_integer(y); return y; } /* initialization of TinyScheme */ #if USE_INTERFACE INTERFACE static pointer s_cons(scheme *sc, pointer a, pointer b) { return cons(sc,a,b); } INTERFACE static pointer s_immutable_cons(scheme *sc, pointer a, pointer b) { return immutable_cons(sc,a,b); } static const struct scheme_interface vtbl = { scheme_define, s_cons, s_immutable_cons, reserve_cells, mk_integer, mk_real, mk_symbol, gensym, mk_string, mk_counted_string, mk_character, mk_vector, mk_foreign_func, mk_foreign_object, get_foreign_object_vtable, get_foreign_object_data, putstr, putcharacter, is_string, string_value, is_number, nvalue, ivalue, rvalue, is_integer, is_real, is_character, charvalue, is_list, is_vector, list_length, ivalue, fill_vector, vector_elem, set_vector_elem, is_port, is_pair, pair_car, pair_cdr, set_car, set_cdr, is_symbol, symname, is_syntax, is_proc, is_foreign, syntaxname, is_closure, is_macro, closure_code, closure_env, is_continuation, is_promise, is_environment, is_immutable, setimmutable, scheme_load_file, scheme_load_string, port_from_file }; #endif -scheme *scheme_init_new() { +scheme *scheme_init_new(void) { scheme *sc=(scheme*)malloc(sizeof(scheme)); if(!scheme_init(sc)) { free(sc); return 0; } else { return sc; } } scheme *scheme_init_new_custom_alloc(func_alloc malloc, func_dealloc free) { scheme *sc=(scheme*)malloc(sizeof(scheme)); if(!scheme_init_custom_alloc(sc,malloc,free)) { free(sc); return 0; } else { return sc; } } int scheme_init(scheme *sc) { return scheme_init_custom_alloc(sc,malloc,free); } int scheme_init_custom_alloc(scheme *sc, func_alloc malloc, func_dealloc free) { int i, n=sizeof(dispatch_table)/sizeof(dispatch_table[0]); pointer x; #if USE_INTERFACE sc->vptr=&vtbl; #endif sc->gensym_cnt=0; sc->malloc=malloc; sc->free=free; sc->sink = &sc->_sink; sc->NIL = &sc->_NIL; sc->T = &sc->_HASHT; sc->F = &sc->_HASHF; sc->EOF_OBJ=&sc->_EOF_OBJ; sc->free_cell = &sc->_NIL; sc->fcells = 0; sc->inhibit_gc = GC_ENABLED; sc->reserved_cells = 0; #ifndef NDEBUG sc->reserved_lineno = 0; #endif sc->no_memory=0; sc->inport=sc->NIL; sc->outport=sc->NIL; sc->save_inport=sc->NIL; sc->loadport=sc->NIL; sc->nesting=0; memset (sc->nesting_stack, 0, sizeof sc->nesting_stack); sc->interactive_repl=0; sc->strbuff = sc->malloc(STRBUFFSIZE); if (sc->strbuff == 0) { sc->no_memory=1; return 0; } sc->strbuff_size = STRBUFFSIZE; sc->cell_segments = NULL; if (alloc_cellseg(sc,FIRST_CELLSEGS) != FIRST_CELLSEGS) { sc->no_memory=1; return 0; } sc->gc_verbose = 0; dump_stack_initialize(sc); sc->code = sc->NIL; sc->tracing=0; sc->flags = 0; /* init sc->NIL */ typeflag(sc->NIL) = (T_NIL | T_ATOM | MARK); car(sc->NIL) = cdr(sc->NIL) = sc->NIL; /* init T */ typeflag(sc->T) = (T_BOOLEAN | T_ATOM | MARK); car(sc->T) = cdr(sc->T) = sc->T; /* init F */ typeflag(sc->F) = (T_BOOLEAN | T_ATOM | MARK); car(sc->F) = cdr(sc->F) = sc->F; /* init EOF_OBJ */ typeflag(sc->EOF_OBJ) = (T_EOF_OBJ | T_ATOM | MARK); car(sc->EOF_OBJ) = cdr(sc->EOF_OBJ) = sc->EOF_OBJ; /* init sink */ typeflag(sc->sink) = (T_SINK | T_PAIR | MARK); car(sc->sink) = cdr(sc->sink) = sc->NIL; /* init c_nest */ sc->c_nest = sc->NIL; sc->oblist = oblist_initial_value(sc); /* init global_env */ new_frame_in_env(sc, sc->NIL); sc->global_env = sc->envir; /* init else */ x = mk_symbol(sc,"else"); new_slot_in_env(sc, x, sc->T); assign_syntax(sc, OP_LAMBDA, "lambda"); assign_syntax(sc, OP_QUOTE, "quote"); assign_syntax(sc, OP_DEF0, "define"); assign_syntax(sc, OP_IF0, "if"); assign_syntax(sc, OP_BEGIN, "begin"); assign_syntax(sc, OP_SET0, "set!"); assign_syntax(sc, OP_LET0, "let"); assign_syntax(sc, OP_LET0AST, "let*"); assign_syntax(sc, OP_LET0REC, "letrec"); assign_syntax(sc, OP_COND0, "cond"); assign_syntax(sc, OP_DELAY, "delay"); assign_syntax(sc, OP_AND0, "and"); assign_syntax(sc, OP_OR0, "or"); assign_syntax(sc, OP_C0STREAM, "cons-stream"); assign_syntax(sc, OP_MACRO0, "macro"); assign_syntax(sc, OP_CASE0, "case"); for(i=0; iLAMBDA = mk_symbol(sc, "lambda"); sc->QUOTE = mk_symbol(sc, "quote"); sc->QQUOTE = mk_symbol(sc, "quasiquote"); sc->UNQUOTE = mk_symbol(sc, "unquote"); sc->UNQUOTESP = mk_symbol(sc, "unquote-splicing"); sc->FEED_TO = mk_symbol(sc, "=>"); sc->COLON_HOOK = mk_symbol(sc,"*colon-hook*"); sc->ERROR_HOOK = mk_symbol(sc, "*error-hook*"); sc->SHARP_HOOK = mk_symbol(sc, "*sharp-hook*"); #if USE_COMPILE_HOOK sc->COMPILE_HOOK = mk_symbol(sc, "*compile-hook*"); #endif return !sc->no_memory; } void scheme_set_input_port_file(scheme *sc, FILE *fin) { sc->inport=port_from_file(sc,fin,port_input); } void scheme_set_input_port_string(scheme *sc, char *start, char *past_the_end) { sc->inport=port_from_string(sc,start,past_the_end,port_input); } void scheme_set_output_port_file(scheme *sc, FILE *fout) { sc->outport=port_from_file(sc,fout,port_output); } void scheme_set_output_port_string(scheme *sc, char *start, char *past_the_end) { sc->outport=port_from_string(sc,start,past_the_end,port_output); } void scheme_set_external_data(scheme *sc, void *p) { sc->ext_data=p; } void scheme_deinit(scheme *sc) { struct cell_segment *s; int i; sc->oblist=sc->NIL; sc->global_env=sc->NIL; dump_stack_free(sc); sc->envir=sc->NIL; sc->code=sc->NIL; history_free(sc); sc->args=sc->NIL; sc->value=sc->NIL; if(is_port(sc->inport)) { typeflag(sc->inport) = T_ATOM; } sc->inport=sc->NIL; sc->outport=sc->NIL; if(is_port(sc->save_inport)) { typeflag(sc->save_inport) = T_ATOM; } sc->save_inport=sc->NIL; if(is_port(sc->loadport)) { typeflag(sc->loadport) = T_ATOM; } sc->loadport=sc->NIL; for(i=0; i<=sc->file_i; i++) { port_clear_location(sc, &sc->load_stack[i]); } sc->gc_verbose=0; gc(sc,sc->NIL,sc->NIL); for (s = sc->cell_segments; s; s = _dealloc_cellseg(sc, s)) { /* nop */ } sc->free(sc->strbuff); } void scheme_load_file(scheme *sc, FILE *fin) { scheme_load_named_file(sc,fin,0); } void scheme_load_named_file(scheme *sc, FILE *fin, const char *filename) { dump_stack_reset(sc); sc->envir = sc->global_env; sc->file_i=0; sc->load_stack[0].kind=port_input|port_file; sc->load_stack[0].rep.stdio.file=fin; sc->loadport=mk_port(sc,sc->load_stack); sc->retcode=0; if(fin==stdin) { sc->interactive_repl=1; } port_init_location(sc, &sc->load_stack[0], (fin != stdin && filename) ? mk_string(sc, filename) : NULL); sc->inport=sc->loadport; sc->args = mk_integer(sc,sc->file_i); Eval_Cycle(sc, OP_T0LVL); typeflag(sc->loadport)=T_ATOM; if(sc->retcode==0) { sc->retcode=sc->nesting!=0; } port_clear_location(sc, &sc->load_stack[0]); } void scheme_load_string(scheme *sc, const char *cmd) { scheme_load_memory(sc, cmd, strlen(cmd), NULL); } void scheme_load_memory(scheme *sc, const char *buf, size_t len, const char *filename) { dump_stack_reset(sc); sc->envir = sc->global_env; sc->file_i=0; sc->load_stack[0].kind=port_input|port_string; sc->load_stack[0].rep.string.start = (char *) buf; /* This func respects const */ sc->load_stack[0].rep.string.past_the_end = (char *) buf + len; sc->load_stack[0].rep.string.curr = (char *) buf; port_init_location(sc, &sc->load_stack[0], filename ? mk_string(sc, filename) : NULL); sc->loadport=mk_port(sc,sc->load_stack); sc->retcode=0; sc->interactive_repl=0; sc->inport=sc->loadport; sc->args = mk_integer(sc,sc->file_i); Eval_Cycle(sc, OP_T0LVL); typeflag(sc->loadport)=T_ATOM; if(sc->retcode==0) { sc->retcode=sc->nesting!=0; } port_clear_location(sc, &sc->load_stack[0]); } void scheme_define(scheme *sc, pointer envir, pointer symbol, pointer value) { pointer x; pointer *sslot; x = find_slot_spec_in_env(sc, envir, symbol, 0, &sslot); if (x != sc->NIL) { set_slot_in_env(sc, x, value); } else { new_slot_spec_in_env(sc, symbol, value, sslot); } } #if !STANDALONE void scheme_register_foreign_func(scheme * sc, scheme_registerable * sr) { scheme_define(sc, sc->global_env, mk_symbol(sc,sr->name), mk_foreign_func(sc, sr->f)); } void scheme_register_foreign_func_list(scheme * sc, scheme_registerable * list, int count) { int i; for(i = 0; i < count; i++) { scheme_register_foreign_func(sc, list + i); } } pointer scheme_apply0(scheme *sc, const char *procname) { return scheme_eval(sc, cons(sc,mk_symbol(sc,procname),sc->NIL)); } void save_from_C_call(scheme *sc) { pointer saved_data = cons(sc, car(sc->sink), cons(sc, sc->envir, sc->dump)); /* Push */ sc->c_nest = cons(sc, saved_data, sc->c_nest); /* Truncate the dump stack so TS will return here when done, not directly resume pre-C-call operations. */ dump_stack_reset(sc); } void restore_from_C_call(scheme *sc) { car(sc->sink) = caar(sc->c_nest); sc->envir = cadar(sc->c_nest); sc->dump = cdr(cdar(sc->c_nest)); /* Pop */ sc->c_nest = cdr(sc->c_nest); } /* "func" and "args" are assumed to be already eval'ed. */ pointer scheme_call(scheme *sc, pointer func, pointer args) { int old_repl = sc->interactive_repl; sc->interactive_repl = 0; save_from_C_call(sc); sc->envir = sc->global_env; sc->args = args; sc->code = func; sc->retcode = 0; Eval_Cycle(sc, OP_APPLY); sc->interactive_repl = old_repl; restore_from_C_call(sc); return sc->value; } pointer scheme_eval(scheme *sc, pointer obj) { int old_repl = sc->interactive_repl; sc->interactive_repl = 0; save_from_C_call(sc); sc->args = sc->NIL; sc->code = obj; sc->retcode = 0; Eval_Cycle(sc, OP_EVAL); sc->interactive_repl = old_repl; restore_from_C_call(sc); return sc->value; } #endif /* ========== Main ========== */ #if STANDALONE #if defined(__APPLE__) && !defined (OSX) int main() { extern MacTS_main(int argc, char **argv); char** argv; int argc = ccommand(&argv); MacTS_main(argc,argv); return 0; } int MacTS_main(int argc, char **argv) { #else int main(int argc, char **argv) { #endif scheme sc; FILE *fin; char *file_name=InitFile; int retcode; int isfile=1; if(argc==1) { printf(banner); } if(argc==2 && strcmp(argv[1],"-?")==0) { printf("Usage: tinyscheme -?\n"); printf("or: tinyscheme [ ...]\n"); printf("followed by\n"); printf(" -1 [ ...]\n"); printf(" -c [ ...]\n"); printf("assuming that the executable is named tinyscheme.\n"); printf("Use - as filename for stdin.\n"); return 1; } if(!scheme_init(&sc)) { fprintf(stderr,"Could not initialize!\n"); return 2; } scheme_set_input_port_file(&sc, stdin); scheme_set_output_port_file(&sc, stdout); #if USE_DL scheme_define(&sc,sc.global_env,mk_symbol(&sc,"load-extension"),mk_foreign_func(&sc, scm_load_ext)); #endif argv++; if(access(file_name,0)!=0) { char *p=getenv("TINYSCHEMEINIT"); if(p!=0) { file_name=p; } } do { if(strcmp(file_name,"-")==0) { fin=stdin; } else if(strcmp(file_name,"-1")==0 || strcmp(file_name,"-c")==0) { pointer args=sc.NIL; isfile=file_name[1]=='1'; file_name=*argv++; if(strcmp(file_name,"-")==0) { fin=stdin; } else if(isfile) { fin=fopen(file_name,"r"); } for(;*argv;argv++) { pointer value=mk_string(&sc,*argv); args=cons(&sc,value,args); } args=reverse_in_place(&sc,sc.NIL,args); scheme_define(&sc,sc.global_env,mk_symbol(&sc,"*args*"),args); } else { fin=fopen(file_name,"r"); } if(isfile && fin==0) { fprintf(stderr,"Could not open file %s\n",file_name); } else { if(isfile) { scheme_load_named_file(&sc,fin,file_name); } else { scheme_load_string(&sc,file_name); } if(!isfile || fin!=stdin) { if(sc.retcode!=0) { fprintf(stderr,"Errors encountered reading %s\n",file_name); } if(isfile) { fclose(fin); } } } file_name=*argv++; } while(file_name!=0); if(argc==1) { scheme_load_named_file(&sc,stdin,0); } retcode=sc.retcode; scheme_deinit(&sc); return retcode; } #endif /* Local variables: c-file-style: "k&r" End: */