diff options
Diffstat (limited to 'sm')
-rw-r--r-- | sm/ChangeLog | 816 | ||||
-rw-r--r-- | sm/Makefile.am | 55 | ||||
-rw-r--r-- | sm/call-agent.c | 713 | ||||
-rw-r--r-- | sm/call-dirmngr.c | 632 | ||||
-rw-r--r-- | sm/certchain.c | 793 | ||||
-rw-r--r-- | sm/certcheck.c | 300 | ||||
-rw-r--r-- | sm/certdump.c | 457 | ||||
-rw-r--r-- | sm/certlist.c | 315 | ||||
-rw-r--r-- | sm/certreqgen.c | 699 | ||||
-rw-r--r-- | sm/decrypt.c | 506 | ||||
-rw-r--r-- | sm/delete.c | 165 | ||||
-rw-r--r-- | sm/encrypt.c | 550 | ||||
-rw-r--r-- | sm/export.c | 249 | ||||
-rw-r--r-- | sm/fingerprint.c | 271 | ||||
-rw-r--r-- | sm/gpgsm.c | 1458 | ||||
-rw-r--r-- | sm/gpgsm.h | 274 | ||||
-rw-r--r-- | sm/import.c | 349 | ||||
-rw-r--r-- | sm/keydb.c | 1282 | ||||
-rw-r--r-- | sm/keylist.c | 617 | ||||
-rw-r--r-- | sm/server.c | 1070 | ||||
-rw-r--r-- | sm/sign.c | 621 | ||||
-rw-r--r-- | sm/verify.c | 550 |
22 files changed, 12742 insertions, 0 deletions
diff --git a/sm/ChangeLog b/sm/ChangeLog new file mode 100644 index 000000000..59a6b3271 --- /dev/null +++ b/sm/ChangeLog @@ -0,0 +1,816 @@ +2003-07-31 Werner Koch <[email protected]> + + * Makefile.am (gpgsm_LDADD): Added INTLLIBS. + +2003-07-29 Werner Koch <[email protected]> + + * gpgsm.c (main): Add secmem features and set the random seed file. + (gpgsm_exit): Update the random seed file and enable debug output. + +2003-07-27 Werner Koch <[email protected]> + + Adjusted for gcry_mpi_print and gcry_mpi_scan API change. + +2003-06-24 Werner Koch <[email protected]> + + * server.c (gpgsm_status_with_err_code): New. + * verify.c (gpgsm_verify): Use it here instead of the old + tokenizing version. + + * verify.c (strtimestamp): Renamed to strtimestamp_r + + Adjusted for changes in the libgcrypt API. Some more fixes for the + libgpg-error stuff. + +2003-06-04 Werner Koch <[email protected]> + + * call-agent.c (init_membuf,put_membuf,get_membuf): Removed. + Include new membuf header and changed used type. + + Renamed error codes from INVALID to INV and removed _ERROR suffixes. + +2003-06-03 Werner Koch <[email protected]> + + Changed all error codes in all files to the new libgpg-error scheme. + + * gpgsm.h: Include gpg-error.h . + * Makefile.am: Link with libgpg-error. + +2003-04-29 Werner Koch <[email protected]> + + * Makefile.am: Use libassuan. Don't override LDFLAGS anymore. + * server.c (register_commands): Adjust for new Assuan semantics. + +2002-12-03 Werner Koch <[email protected]> + + * call-agent.c (gpgsm_agent_passwd): New. + * gpgsm.c (main): New command --passwd and --call-protect-tool + (run_protect_tool): New. + +2002-11-25 Werner Koch <[email protected]> + + * verify.c (gpgsm_verify): Handle content-type attribute. + +2002-11-13 Werner Koch <[email protected]> + + * call-agent.c (start_agent): Try to use $GPG_TTY instead of + ttyname. Changed ttyname to test stdin becuase it can be assumed + that output redirection is more common that input redirection. + +2002-11-12 Werner Koch <[email protected]> + + * gpgsm.c: New command --call-dirmngr. + * call-dirmngr.c (gpgsm_dirmngr_run_command) + (run_command_inq_cb,run_command_cb) + (run_command_status_cb): New. + +2002-11-11 Werner Koch <[email protected]> + + * certcheck.c (gpgsm_check_cms_signature): Don't double free + s_sig but free s_pkey at leave. + +2002-11-10 Werner Koch <[email protected]> + + * gpgsm.c: Removed duplicate --list-secret-key entry. + +2002-09-19 Werner Koch <[email protected]> + + * certcheck.c (gpgsm_check_cert_sig): Add cert hash debugging. + + * certchain.c (find_up): Print info when the cert was not found + by the autorithyKeyIdentifier. + +2002-09-03 Werner Koch <[email protected]> + + * gpgsm.c (main): Disable the internal libgcrypt locking. + +2002-08-21 Werner Koch <[email protected]> + + * import.c (print_imported_summary): Cleaned up. Print new + not_imported value. + (check_and_store): Update non_imported counter. + (print_import_problem): New. + (check_and_store): Print error status message. + * server.c (get_status_string): Added STATUS_IMPORT_PROBLEM. + +2002-08-20 Werner Koch <[email protected]> + + * gpgsm.c (main): Use the log file only in server mode. + + * import.c (print_imported_summary): New. + (check_and_store): Update the counters, take new argument. + (import_one): Factored out core of gpgsm_import. + (gpgsm_import): Print counters. + (gpgsm_import_files): New. + * gpgsm.c (main): Use the new function for import. + +2002-08-19 Werner Koch <[email protected]> + + * decrypt.c (gpgsm_decrypt): Return a better error status token. + * verify.c (gpgsm_verify): Don't error on messages with no signing + time or no message digest. This is only the case for messages + without any signed attributes. + +2002-08-16 Werner Koch <[email protected]> + + * certpath.c: Renamed to .. + * certchain.c: this. Renamed all all other usages of "path" in the + context of certificates to "chain". + + * call-agent.c (learn_cb): Special treatment when the issuer + certificate is missing. + +2002-08-10 Werner Koch <[email protected]> + + * Makefile.am (INCLUDES): Add definition for localedir. + + * keylist.c (list_cert_colon): Print the short fingerprint in the + key ID field. + * fingerprint.c (gpgsm_get_short_fingerprint): New. + * verify.c (gpgsm_verify): Print more verbose info for a good + signature. + +2002-08-09 Werner Koch <[email protected]> + + * decrypt.c (prepare_decryption): Hack to detected already + unpkcsedone keys. + + * gpgsm.c (emergency_cleanup): New. + (main): Initialize the signal handler. + + * sign.c (gpgsm_sign): Reset the hash context for subsequent + signers and release it at the end. + +2002-08-05 Werner Koch <[email protected]> + + * server.c (cmd_signer): New command "SIGNER" + (register_commands): Register it. + (cmd_sign): Pass the signer list to gpgsm_sign. + * certlist.c (gpgsm_add_to_certlist): Add SECRET argument, check + for secret key if set and changed all callers. + * sign.c (gpgsm_sign): New argument SIGNERLIST and implemt + multiple signers. + * gpgsm.c (main): Support more than one -u. + + * server.c (cmd_recipient): Return reason code 1 for No_Public_Key + which is actually what gets returned from add_to_certlist. + +2002-07-26 Werner Koch <[email protected]> + + * certcheck.c (gpgsm_check_cert_sig): Implement proper cleanup. + (gpgsm_check_cms_signature): Ditto. + +2002-07-22 Werner Koch <[email protected]> + + * keydb.c (keydb_add_resource): Register a lock file. + (lock_all, unlock_all): Implemented. + + * delete.c: New. + * gpgsm.c: Made --delete-key work. + * server.c (cmd_delkeys): New. + (register_commands): New command DELKEYS. + + * decrypt.c (gpgsm_decrypt): Print a convenience note when RC2 is + used and a STATUS_ERROR with the algorithm oid. + +2002-07-03 Werner Koch <[email protected]> + + * server.c (gpgsm_status2): Insert a blank between all optional + arguments when using assuan. + * server.c (cmd_recipient): No more need for extra blank in constants. + * import.c (print_imported_status): Ditto. + * gpgsm.c (main): Ditto. + +2002-07-02 Werner Koch <[email protected]> + + * verify.c (gpgsm_verify): Extend the STATUS_BADSIG line with + the fingerprint. + + * certpath.c (check_cert_policy): Don't use log_error to print a + warning. + + * keydb.c (keydb_store_cert): Add optional ar EXISTED and changed + all callers. + * call-agent.c (learn_cb): Print info message only for real imports. + + * import.c (gpgsm_import): Moved duplicated code to ... + (check_and_store): new function. Added magic to import the entire + chain. Print status only for real imports and moved printing code + to .. + (print_imported_status): New. + + * call-dirmngr.c (gpgsm_dirmngr_isvalid): print status of dirmngr + call in very verbose mode. + + * gpgsm.c (main): Use the same error codes for STATUS_INV_RECP as + with the server mode. + +2002-06-29 Werner Koch <[email protected]> + + * gpgsm.c: New option --auto-issuer-key-retrieve. + * certpath.c (find_up): Try to retrieve an issuer key from an + external source and from the ephemeral key DB. + (find_up_store_certs_cb): New. + + * keydb.c (keydb_set_ephemeral): Does now return the old + state. Call the backend only when required. + + * call-dirmngr.c (start_dirmngr): Use GNUPG_DEFAULT_DIRMNGR. + (lookup_status_cb): Issue status only when CTRL is not NULL. + (gpgsm_dirmngr_lookup): Document that CTRL is optional. + + * call-agent.c (start_agent): Use GNUPG_DEFAULT_AGENT. + +2002-06-28 Werner Koch <[email protected]> + + * server.c (cmd_recipient): Add more reason codes. + +2002-06-27 Werner Koch <[email protected]> + + * certpath.c (gpgsm_basic_cert_check): Use + --debug-no-path-validation to also bypass this basic check. + + * gpgsm.c (main): Use GNUPG_DEFAULT_HOMEDIR constant. + + * call-agent.c (start_agent): Create and pass the list of FD to + keep in the child to assuan. + * call-dirmngr.c (start_dirmngr): Ditto. + +2002-06-26 Werner Koch <[email protected]> + + * import.c (gpgsm_import): Print an STATUS_IMPORTED. + + * gpgsm.c: --debug-no-path-validation does not take an argument. + +2002-06-25 Werner Koch <[email protected]> + + * certdump.c (print_dn_part): Always print a leading slash, + removed NEED_DELIM arg and changed caller. + + * export.c (gpgsm_export): Print LFs to FP and not stdout. + (print_short_info): Ditto. Make use of gpgsm_print_name. + + * server.c (cmd_export): Use output-fd instead of data lines; this + was actually the specified way. + +2002-06-24 Werner Koch <[email protected]> + + * gpgsm.c: Removed duped help entry for --list-keys. + + * gpgsm.c, gpgsm.h: New option --debug-no-path-validation. + + * certpath.c (gpgsm_validate_path): Use it here instead of the + debug flag hack. + + * certpath.c (check_cert_policy): Return No_Policy_Match if the + policy file could not be opened. + +2002-06-20 Werner Koch <[email protected]> + + * certlist.c (gpgsm_add_to_certlist): Fixed locating of a + certificate with the required key usage. + + * gpgsm.c (main): Fixed a segv when using --outfile without an + argument. + + * keylist.c (print_capabilities): Also check for non-repudiation + and data encipherment. + * certlist.c (cert_usage_p): Test for signing and encryption was + swapped. Add a case for certification usage, handle + non-repudiation and data encipherment. + (gpgsm_cert_use_cert_p): New. + (gpgsm_add_to_certlist): Added a CTRL argument and changed all + callers to pass it. + * certpath.c (gpgsm_validate_path): Use it here to print a status + message. Added a CTRL argument and changed all callers to pass it. + * decrypt.c (gpgsm_decrypt): Print a status message for wrong key + usage. + * verify.c (gpgsm_verify): Ditto. + * keydb.c (classify_user_id): Allow a colon delimited fingerprint. + +2002-06-19 Werner Koch <[email protected]> + + * call-agent.c (learn_cb): Use log_info instead of log_error on + successful import. + + * keydb.c (keydb_set_ephemeral): New. + (keydb_store_cert): New are ephemeral, changed all callers. + * keylist.c (list_external_cb): Store cert as ephemeral. + * export.c (gpgsm_export): Kludge to export epehmeral certificates. + + * gpgsm.c (main): New command --list-external-keys. + +2002-06-17 Werner Koch <[email protected]> + + * certreqgen.c (read_parameters): Improved error handling. + (gpgsm_genkey): Print error message. + +2002-06-13 Werner Koch <[email protected]> + + * gpgsm.c (main): New option --log-file. + +2002-06-12 Werner Koch <[email protected]> + + * call-dirmngr.c (lookup_status_cb): New. + (gpgsm_dirmngr_lookup): Use the status CB. Add new arg CTRL and + changed caller to pass it. + + * gpgsm.c (open_fwrite): New. + (main): Allow --output for --verify. + + * sign.c (hash_and_copy_data): New. + (gpgsm_sign): Implemented normal (non-detached) signatures. + * gpgsm.c (main): Ditto. + + * certpath.c (gpgsm_validate_path): Special error handling for + no policy match. + +2002-06-10 Werner Koch <[email protected]> + + * server.c (get_status_string): Add STATUS_ERROR. + + * certpath.c (gpgsm_validate_path): Tweaked the error checking to + return error codes in a more sensitive way. + * verify.c (gpgsm_verify): Send status TRUST_NEVER also for a bad + CA certificate and when the certificate has been revoked. Issue + TRUST_FULLY even when the cert has expired. Append an error token + to these status lines. Issue the new generic error status when a + cert was not found and when leaving the function. + +2002-06-04 Werner Koch <[email protected]> + + * gpgsm.c (main): New command --list-sigs + * keylist.c (list_cert_std): New. Use it whenever colon mode is + not used. + (list_cert_chain): New. + +2002-05-31 Werner Koch <[email protected]> + + * gpgsm.c (main): Don't print the "go ahead" message for an + invalid command. + +2002-05-23 Werner Koch <[email protected]> + + * import.c (gpgsm_import): Add error messages. + +2002-05-21 Werner Koch <[email protected]> + + * keylist.c (list_internal_keys): Renamed from gpgsm_list_keys. + (list_external_keys): New. + (gpgsm_list_keys): Dispatcher for above. + * call-dirmngr.c (lookup_cb,pattern_from_strlist) + (gpgsm_dirmngr_lookup): New. + * server.c (option_handler): Handle new option --list-mode. + (do_listkeys): Handle options and actually use the mode argument. + (get_status_string): New code TRUNCATED. + + * import.c (gpgsm_import): Try to identify the type of input and + handle certs-only messages. + +2002-05-14 Werner Koch <[email protected]> + + * gpgsm.c: New option --faked-system-time + * sign.c (gpgsm_sign): And use it here. + * certpath.c (gpgsm_validate_path): Ditto. + +2002-05-03 Werner Koch <[email protected]> + + * certpath.c (gpgsm_validate_path): Added EXPTIME arg and changed + all callers. + * verify.c (gpgsm_verify): Tweaked usage of log_debug and + log_error. Return EXPSIG status and add expiretime to VALIDSIG. + +2002-04-26 Werner Koch <[email protected]> + + * gpgsm.h (DBG_AGENT,DBG_AGENT_VALUE): Replaced by DBG_ASSUAN_*. + Changed all users. + + * call-agent.c (start_agent): Be more silent without -v. + * call-dirmngr.c (start_dirmngr): Ditto. + +2002-04-25 Werner Koch <[email protected]> + + * call-agent.c (start_agent): Make copies of old locales and check + for setlocale. + +2002-04-25 Marcus Brinkmann <[email protected]> + + * call-agent.c (start_agent): Fix error handling logic so the + locale is always correctly reset. + +2002-04-25 Marcus Brinkmann <[email protected]> + + * server.c (option_handler): Accept display, ttyname, ttytype, + lc_ctype and lc_messages options. + * gpgsm.c (main): Allocate memory for these options. + * gpgsm.h (struct opt): Make corresponding members non-const. + +2002-04-24 Marcus Brinkmann <[email protected]> + + * gpgsm.h (struct opt): New members display, ttyname, ttytype, + lc_ctype, lc_messages. + * gpgsm.c (enum cmd_and_opt_values): New members oDisplay, + oTTYname, oTTYtype, oLCctype, oLCmessages. + (opts): New entries for these options. + (main): Handle these new options. + * call-agent.c (start_agent): Set the various display and tty + parameter after resetting. + +2002-04-18 Werner Koch <[email protected]> + + * certreqgen.c (gpgsm_genkey): Write status output on success. + +2002-04-15 Werner Koch <[email protected]> + + * gpgsm.c (main): Check ksba version. + + * certpath.c (find_up): New to use the authorithKeyIdentifier. + Use it in all other functions to locate the signing cert.. + +2002-04-11 Werner Koch <[email protected]> + + * certlist.c (cert_usable_p): New. + (gpgsm_cert_use_sign_p,gpgsm_cert_use_encrypt_p): New. + (gpgsm_cert_use_verify_p,gpgsm_cert_use_decrypt_p): New. + (gpgsm_add_to_certlist): Check the key usage. + * sign.c (gpgsm_sign): Ditto. + * verify.c (gpgsm_verify): Print a message wehn an unsuitable + certificate was used. + * decrypt.c (gpgsm_decrypt): Ditto + * keylist.c (print_capabilities): Determine values from the cert. + +2002-03-28 Werner Koch <[email protected]> + + * keylist.c (list_cert_colon): Fixed listing of crt record; the + issuer is not at the right place. Print a chainingID. + * certpath.c (gpgsm_walk_cert_chain): Be a bit more silent on + common errors. + +2002-03-21 Werner Koch <[email protected]> + + * export.c: New. + * gpgsm.c: Add command --export. + * server.c (cmd_export): New. + +2002-03-13 Werner Koch <[email protected]> + + * decrypt.c (gpgsm_decrypt): Allow multiple recipients. + +2002-03-12 Werner Koch <[email protected]> + + * certpath.c (check_cert_policy): Print the policy list. + + * verify.c (gpgsm_verify): Detect certs-only message. + +2002-03-11 Werner Koch <[email protected]> + + * import.c (gpgsm_import): Print a notice about imported certificates + when in verbose mode. + + * gpgsm.c (main): Print INV_RECP status. + * server.c (cmd_recipient): Ditto. + + * server.c (gpgsm_status2): New. Allows for a list of strings. + (gpgsm_status): Divert to gpgsm_status2. + + * encrypt.c (gpgsm_encrypt): Don't use a default key when no + recipients are given. Print a NO_RECP status. + +2002-03-06 Werner Koch <[email protected]> + + * server.c (cmd_listkeys, cmd_listsecretkeys): Divert to + (do_listkeys): new. Add pattern parsing. + + * keylist.c (gpgsm_list_keys): Handle selection pattern. + + * gpgsm.c: New command --learn-card + * call-agent.c (learn_cb,gpgsm_agent_learn): New. + + * gpgsm.c (main): Print error messages for non-implemented commands. + + * base64.c (base64_reader_cb): Use case insensitive compare of the + Content-Type string to detect plain base-64. + +2002-03-05 Werner Koch <[email protected]> + + * gpgsm.c, gpgsm.h: Add local_user. + * sign.c (gpgsm_get_default_cert): New. + (get_default_signer): Use the new function if local_user is not + set otherwise used that value. + * encrypt.c (get_default_recipient): Removed. + (gpgsm_encrypt): Use gpgsm_get_default_cert. + + * verify.c (gpgsm_verify): Better error text for a bad signature + found by comparing the hashs. + +2002-02-27 Werner Koch <[email protected]> + + * call-dirmngr.c, call-agent.c: Add 2 more arguments to all uses + of assuan_transact. + +2002-02-25 Werner Koch <[email protected]> + + * server.c (option_handler): Allow to use -2 for "send all certs + except the root cert". + * sign.c (add_certificate_list): Implement it here. + * certpath.c (gpgsm_is_root_cert): New. + +2002-02-19 Werner Koch <[email protected]> + + * certpath.c (check_cert_policy): New. + (gpgsm_validate_path): And call it from here. + * gpgsm.c (main): New options --policy-file, + --disable-policy-checks and --enable-policy-checks. + * gpgsm.h (opt): Added policy_file, no_policy_checks. + +2002-02-18 Werner Koch <[email protected]> + + * certpath.c (gpgsm_validate_path): Ask the agent to add the + certificate into the trusted list. + * call-agent.c (gpgsm_agent_marktrusted): New. + +2002-02-07 Werner Koch <[email protected]> + + * certlist.c (gpgsm_add_to_certlist): Check that the specified + name identifies a certificate unambiguously. + (gpgsm_find_cert): Ditto. + + * server.c (cmd_listkeys): Check that the data stream is available. + (cmd_listsecretkeys): Ditto. + (has_option): New. + (cmd_sign): Fix ambiguousity in option recognition. + + * gpgsm.c (main): Enable --logger-fd. + + * encrypt.c (gpgsm_encrypt): Increased buffer size for better + performance. + + * call-agent.c (gpgsm_agent_pksign): Check the S-Exp received from + the agent. + + * keylist.c (list_cert_colon): Filter out control characters. + +2002-02-06 Werner Koch <[email protected]> + + * decrypt.c (gpgsm_decrypt): Bail out after an decryption error. + + * server.c (reset_notify): Close input and output FDs. + (cmd_encrypt,cmd_decrypt,cmd_verify,cmd_sign.cmd_import) + (cmd_genkey): Close the FDs and release the recipient list even in + the error case. + +2002-02-01 Marcus Brinkmann <[email protected]> + + * sign.c (gpgsm_sign): Do not release certificate twice. + +2002-01-29 Werner Koch <[email protected]> + + * call-agent.c (gpgsm_agent_havekey): New. + * keylist.c (list_cert_colon): New arg HAVE_SECRET, print "crs" + when we know that the secret key is available. + (gpgsm_list_keys): New arg MODE, check whether a secret key is + available. Changed all callers. + * gpgsm.c (main): New command --list-secret-keys. + * server.c (cmd_listsecretkeys): New. + (cmd_listkeys): Return secret keys with "crs" record. + +2002-01-28 Werner Koch <[email protected]> + + * certreqgen.c (create_request): Store the email address in the req. + +2002-01-25 Werner Koch <[email protected]> + + * gpgsm.c (main): Disable core dumps. + + * sign.c (add_certificate_list): New. + (gpgsm_sign): Add the certificates to the CMS object. + * certpath.c (gpgsm_walk_cert_chain): New. + * gpgsm.h (server_control_s): Add included_certs. + * gpgsm.c: Add option --include-certs. + (gpgsm_init_default_ctrl): New. + (main): Call it. + * server.c (gpgsm_server): Ditto. + (option_handler): Support --include-certs. + +2002-01-23 Werner Koch <[email protected]> + + * certpath.c (gpgsm_validate_path): Print the DN of a missing issuer. + * certdump.c (gpgsm_dump_string): New. + (print_dn): Replaced by above. + +2002-01-22 Werner Koch <[email protected]> + + * certpath.c (unknown_criticals): New. + (allowed_ca): New. + (gpgsm_validate_path): Check validity, CA attribute, path length + and unknown critical extensions. + +2002-01-21 Werner Koch <[email protected]> + + * gpgsm.c: Add option --enable-crl-checks. + + * call-agent.c (start_agent): Implemented socket based access. + * call-dirmngr.c (start_dirmngr): Ditto. + +2002-01-20 Werner Koch <[email protected]> + + * server.c (option_handler): New. + (gpgsm_server): Register it with assuan. + +2002-01-19 Werner Koch <[email protected]> + + * server.c (gpgsm_server): Use assuan_deinit_server and setup + assuan logging if enabled. + * call-agent.c (inq_ciphertext_cb): Don't show the session key in + an Assuan log file. + + * gpgsm.c (my_strusage): Take bugreport address from configure.ac + +2002-01-15 Werner Koch <[email protected]> + + * import.c (gpgsm_import): Just do a basic cert check before + storing it. + * certpath.c (gpgsm_basic_cert_check): New. + + * keydb.c (keydb_store_cert): New. + * import.c (store_cert): Removed and change all caller to use + the new function. + * verify.c (store_cert): Ditto. + + * certlist.c (gpgsm_add_to_certlist): Validate the path + + * certpath.c (gpgsm_validate_path): Check the trust list. + * call-agent.c (gpgsm_agent_istrusted): New. + +2002-01-14 Werner Koch <[email protected]> + + * call-dirmngr.c (inq_certificate): Changed for new interface semantic. + * certlist.c (gpgsm_find_cert): New. + +2002-01-13 Werner Koch <[email protected]> + + * fingerprint.c (gpgsm_get_certid): Print the serial and not the + hash after the dot. + +2002-01-11 Werner Koch <[email protected]> + + * call-dirmngr.c: New. + * certpath.c (gpgsm_validate_path): Check the CRL here. + * fingerprint.c (gpgsm_get_certid): New. + * gpgsm.c: New options --dirmngr-program and --disable-crl-checks. + +2002-01-10 Werner Koch <[email protected]> + + * base64.c (gpgsm_create_writer): Allow to set the object name + +2002-01-08 Werner Koch <[email protected]> + + * keydb.c (spacep): Removed because it is now in util.c + + * server.c (cmd_genkey): New. + * certreqgen.c: New. The parameter handling code has been taken + from gnupg/g10/keygen.c version 1.0.6. + * call-agent.c (gpgsm_agent_genkey): New. + +2002-01-02 Werner Koch <[email protected]> + + * server.c (rc_to_assuan_status): Removed and changed all callers + to use map_to_assuan_status. + +2001-12-20 Werner Koch <[email protected]> + + * verify.c (gpgsm_verify): Implemented non-detached signature + verification. Add OUT_FP arg, initialize a writer and changed all + callers. + * server.c (cmd_verify): Pass an out_fp if one has been set. + + * base64.c (base64_reader_cb): Try to detect an S/MIME body part. + + * certdump.c (print_sexp): Renamed to gpgsm_dump_serial, made + global. + (print_time): Renamed to gpgsm_dump_time, made global. + (gpgsm_dump_serial): Take a real S-Expression as argument and + print the first item. + * keylist.c (list_cert_colon): Ditto. + * keydb.c (keydb_search_issuer_sn): Ditto. + * decrypt.c (print_integer_sexp): Removed and made callers + use gpgsm_dump_serial. + * verify.c (print_time): Removed, made callers use gpgsm_dump_time. + +2001-12-19 Marcus Brinkmann <[email protected]> + + * call-agent.c (start_agent): Add new argument to assuan_pipe_connect. + +2001-12-18 Werner Koch <[email protected]> + + * verify.c (print_integer_sexp): Renamed from print_integer and + print the serial number according to the S-Exp rules. + * decrypt.c (print_integer_sexp): Ditto. + +2001-12-17 Werner Koch <[email protected]> + + * keylist.c (list_cert_colon): Changed for new return value of + get_serial. + * keydb.c (keydb_search_issuer_sn): Ditto. + * certcheck.c (gpgsm_check_cert_sig): Likewise for other S-Exp + returingin functions. + * fingerprint.c (gpgsm_get_keygrip): Ditto. + * encrypt.c (encrypt_dek): Ditto + * certcheck.c (gpgsm_check_cms_signature): Ditto + * decrypt.c (prepare_decryption): Ditto. + * call-agent.c (gpgsm_agent_pkdecrypt): Removed arg ciphertextlen, + use KsbaSexp type and calculate the length. + + * certdump.c (print_sexp): Remaned from print_integer, changed caller. + + * Makefile.am: Use the LIBGCRYPT and LIBKSBA variables. + + * fingerprint.c (gpgsm_get_keygrip): Use the new + gcry_pk_get_keygrip to calculate the grip - note the algorithm and + therefore the grip values changed. + +2001-12-15 Werner Koch <[email protected]> + + * certcheck.c (gpgsm_check_cms_signature): Removed the faked-key + kludge. + (gpgsm_create_cms_signature): Removed the commented fake key + code. This makes the function pretty simple. + + * gpgsm.c (main): Renamed the default key database to "keyring.kbx". + + * decrypt.c (gpgsm_decrypt): Write STATUS_DECRYPTION_*. + * sign.c (gpgsm_sign): Write a STATUS_SIG_CREATED. + +2001-12-14 Werner Koch <[email protected]> + + * keylist.c (list_cert_colon): Kludge to show an email address + encoded in the subject's DN. + + * verify.c (gpgsm_verify): Add hash debug helpers + * sign.c (gpgsm_sign): Ditto. + + * base64.c (base64_reader_cb): Reset the linelen when we need to + skip the line and adjusted test; I somehow forgot about DeMorgan. + + * server.c (cmd_encrypt,cmd_decrypt,cmd_sign,cmd_verify) + (cmd_import): Close the FDs on success. + (close_message_fd): New. + (input_notify): Setting autodetect_encoding to 0 after initializing + it to 0 is pretty pointless. Easy to fix. + + * gpgsm.c (main): New option --debug-wait n, so that it is + possible to attach gdb when used in server mode. + + * sign.c (get_default_signer): Use keydb_classify_name here. + +2001-12-14 Marcus Brinkmann <[email protected]> + + * call-agent.c (LINELENGTH): Removed. + (gpgsm_agent_pksign): Use ASSUAN_LINELENGTH, not LINELENGTH. + (gpgsm_agent_pkdecrypt): Likewise. + +2001-12-13 Werner Koch <[email protected]> + + * keylist.c (list_cert_colon): Print alternative names of subject + and a few other values. + +2001-12-12 Werner Koch <[email protected]> + + * gpgsm.c (main): New options --assume-{armor,base64,binary}. + * base64.c (base64_reader_cb): Fixed non-autodetection mode. + +2001-12-04 Werner Koch <[email protected]> + + * call-agent.c (read_from_agent): Check for inquire responses. + (request_reply): Handle them using a new callback arg, changed all + callers. + (gpgsm_agent_pkdecrypt): New. + +2001-11-27 Werner Koch <[email protected]> + + * base64.c: New. Changed all other functions to use this instead + of direct creation of ksba_reader/writer. + * gpgsm.c (main): Set ctrl.auto_encoding unless --no-armor is used. + +2001-11-26 Werner Koch <[email protected]> + + * gpgsm.c: New option --agent-program + * call-agent.c (start_agent): Allow to override the default path + to the agent. + + * keydb.c (keydb_add_resource): Create keybox + + * keylist.c (gpgsm_list_keys): Fixed non-server keylisting. + + * server.c (rc_to_assuan_status): New. Use it for all commands. + + + Copyright 2001, 2002, 2003 Free Software Foundation, Inc. + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/sm/Makefile.am b/sm/Makefile.am new file mode 100644 index 000000000..6345573e5 --- /dev/null +++ b/sm/Makefile.am @@ -0,0 +1,55 @@ +# Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GnuPG is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +## Process this file with automake to produce Makefile.in + +localedir = $(datadir)/locale +INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\" + +bin_PROGRAMS = gpgsm + +AM_CPPFLAGS = -I$(top_srcdir)/common -I$(top_srcdir)/intl \ + $(LIBGCRYPT_CFLAGS) $(LIBASSUAN_CFLAGS) $(KSBA_CFLAGS) + +gpgsm_SOURCES = \ + gpgsm.c gpgsm.h \ + misc.c \ + keydb.c keydb.h \ + server.c \ + call-agent.c \ + call-dirmngr.c \ + fingerprint.c \ + base64.c \ + certlist.c \ + certdump.c \ + certcheck.c \ + certchain.c \ + keylist.c \ + verify.c \ + sign.c \ + encrypt.c \ + decrypt.c \ + import.c \ + export.c \ + delete.c \ + certreqgen.c + + +gpgsm_LDADD = ../jnlib/libjnlib.a ../kbx/libkeybox.a ../common/libcommon.a \ + $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(KSBA_LIBS) -lgpg-error \ + @INTLLIBS@ diff --git a/sm/call-agent.c b/sm/call-agent.c new file mode 100644 index 000000000..4d26e3450 --- /dev/null +++ b/sm/call-agent.c @@ -0,0 +1,713 @@ +/* call-agent.c - divert operations to the agent + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> +#ifdef HAVE_LOCALE_H +#include <locale.h> +#endif + +#include "gpgsm.h" +#include <gcrypt.h> +#include <assuan.h> +#include "i18n.h" +#include "keydb.h" /* fixme: Move this to import.c */ +#include "../common/membuf.h" + + +static ASSUAN_CONTEXT agent_ctx = NULL; +static int force_pipe_server = 0; + +struct cipher_parm_s { + ASSUAN_CONTEXT ctx; + const char *ciphertext; + size_t ciphertextlen; +}; + +struct genkey_parm_s { + ASSUAN_CONTEXT ctx; + const char *sexp; + size_t sexplen; +}; + +struct learn_parm_s { + int error; + ASSUAN_CONTEXT ctx; + membuf_t *data; +}; + + + +/* Try to connect to the agent via socket or fork it off and work by + pipes. Handle the server's initial greeting */ +static int +start_agent (void) +{ + int rc = 0; + char *infostr, *p; + ASSUAN_CONTEXT ctx; + char *dft_display = NULL; + char *dft_ttyname = NULL; + char *dft_ttytype = NULL; + char *old_lc = NULL; + char *dft_lc = NULL; + + if (agent_ctx) + return 0; /* fixme: We need a context for each thread or serialize + the access to the agent (which is suitable given that + the agent is not MT */ + + infostr = force_pipe_server? NULL : getenv ("GPG_AGENT_INFO"); + if (!infostr) + { + const char *pgmname; + const char *argv[3]; + int no_close_list[3]; + int i; + + if (opt.verbose) + log_info (_("no running gpg-agent - starting one\n")); + + if (fflush (NULL)) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("error flushing pending output: %s\n", strerror (errno)); + return tmperr; + } + + if (!opt.agent_program || !*opt.agent_program) + opt.agent_program = GNUPG_DEFAULT_AGENT; + if ( !(pgmname = strrchr (opt.agent_program, '/'))) + pgmname = opt.agent_program; + else + pgmname++; + + argv[0] = pgmname; + argv[1] = "--server"; + argv[2] = NULL; + + i=0; + if (log_get_fd () != -1) + no_close_list[i++] = log_get_fd (); + no_close_list[i++] = fileno (stderr); + no_close_list[i] = -1; + + /* connect to the agent and perform initial handshaking */ + rc = assuan_pipe_connect (&ctx, opt.agent_program, (char**)argv, + no_close_list); + } + else + { + int prot; + int pid; + + infostr = xstrdup (infostr); + if ( !(p = strchr (infostr, ':')) || p == infostr) + { + log_error (_("malformed GPG_AGENT_INFO environment variable\n")); + xfree (infostr); + force_pipe_server = 1; + return start_agent (); + } + *p++ = 0; + pid = atoi (p); + while (*p && *p != ':') + p++; + prot = *p? atoi (p+1) : 0; + if (prot != 1) + { + log_error (_("gpg-agent protocol version %d is not supported\n"), + prot); + xfree (infostr); + force_pipe_server = 1; + return start_agent (); + } + + rc = assuan_socket_connect (&ctx, infostr, pid); + xfree (infostr); + if (rc == ASSUAN_Connect_Failed) + { + log_error (_("can't connect to the agent - trying fall back\n")); + force_pipe_server = 1; + return start_agent (); + } + } + + if (rc) + { + log_error ("can't connect to the agent: %s\n", assuan_strerror (rc)); + return gpg_error (GPG_ERR_NO_AGENT); + } + agent_ctx = ctx; + + if (DBG_ASSUAN) + log_debug ("connection to agent established\n"); + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + dft_display = getenv ("DISPLAY"); + if (opt.display || dft_display) + { + char *optstr; + if (asprintf (&optstr, "OPTION display=%s", + opt.display ? opt.display : dft_display) < 0) + return OUT_OF_CORE (errno); + rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (rc) + return map_assuan_err (rc); + } + if (!opt.ttyname) + { + dft_ttyname = getenv ("GPG_TTY"); + if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) + dft_ttyname = ttyname (0); + } + if (opt.ttyname || dft_ttyname) + { + char *optstr; + if (asprintf (&optstr, "OPTION ttyname=%s", + opt.ttyname ? opt.ttyname : dft_ttyname) < 0) + return OUT_OF_CORE (errno); + rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (rc) + return map_assuan_err (rc); + } + dft_ttytype = getenv ("TERM"); + if (opt.ttytype || (dft_ttyname && dft_ttytype)) + { + char *optstr; + if (asprintf (&optstr, "OPTION ttytype=%s", + opt.ttyname ? opt.ttytype : dft_ttytype) < 0) + return OUT_OF_CORE (errno); + rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (rc) + return map_assuan_err (rc); + } +#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) + old_lc = setlocale (LC_CTYPE, NULL); + if (old_lc) + { + old_lc = strdup (old_lc); + if (!old_lc) + return OUT_OF_CORE (errno); + } + dft_lc = setlocale (LC_CTYPE, ""); +#endif + if (opt.lc_ctype || (dft_ttyname && dft_lc)) + { + char *optstr; + if (asprintf (&optstr, "OPTION lc-ctype=%s", + opt.lc_ctype ? opt.lc_ctype : dft_lc) < 0) + rc = OUT_OF_CORE (errno); + else + { + rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (rc) + rc = map_assuan_err (rc); + } + } +#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) + if (old_lc) + { + setlocale (LC_CTYPE, old_lc); + free (old_lc); + } +#endif + if (rc) + return rc; +#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) + old_lc = setlocale (LC_MESSAGES, NULL); + if (old_lc) + { + old_lc = strdup (old_lc); + if (!old_lc) + return OUT_OF_CORE (errno); + } + dft_lc = setlocale (LC_MESSAGES, ""); +#endif + if (opt.lc_messages || (dft_ttyname && dft_lc)) + { + char *optstr; + if (asprintf (&optstr, "OPTION lc-messages=%s", + opt.lc_messages ? opt.lc_messages : dft_lc) < 0) + rc = OUT_OF_CORE (errno); + else + { + rc = assuan_transact (agent_ctx, optstr, NULL, NULL, NULL, NULL, NULL, + NULL); + free (optstr); + if (rc) + rc = map_assuan_err (rc); + } + } +#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) + if (old_lc) + { + setlocale (LC_MESSAGES, old_lc); + free (old_lc); + } +#endif + + return rc; +} + + +static AssuanError +membuf_data_cb (void *opaque, const void *buffer, size_t length) +{ + membuf_t *data = opaque; + + if (buffer) + put_membuf (data, buffer, length); + return 0; +} + + + + +/* Call the agent to do a sign operation using the key identified by + the hex string KEYGRIP. */ +int +gpgsm_agent_pksign (const char *keygrip, + unsigned char *digest, size_t digestlen, int digestalgo, + char **r_buf, size_t *r_buflen ) +{ + int rc, i; + char *p, line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + + *r_buf = NULL; + rc = start_agent (); + if (rc) + return rc; + + if (digestlen*2 + 50 > DIM(line)) + return gpg_error (GPG_ERR_GENERAL); + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + snprintf (line, DIM(line)-1, "SIGKEY %s", keygrip); + line[DIM(line)-1] = 0; + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + sprintf (line, "SETHASH %d ", digestalgo); + p = line + strlen (line); + for (i=0; i < digestlen ; i++, p += 2 ) + sprintf (p, "%02X", digest[i]); + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + init_membuf (&data, 1024); + rc = assuan_transact (agent_ctx, "PKSIGN", + membuf_data_cb, &data, NULL, NULL, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return map_assuan_err (rc); + } + *r_buf = get_membuf (&data, r_buflen); + + if (!gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL)) + { + xfree (*r_buf); *r_buf = NULL; + return gpg_error (GPG_ERR_INV_VALUE); + } + + return *r_buf? 0 : OUT_OF_CORE (errno); +} + + + + +/* Handle a CIPHERTEXT inquiry. Note, we only send the data, + assuan_transact talkes care of flushing and writing the end */ +static AssuanError +inq_ciphertext_cb (void *opaque, const char *keyword) +{ + struct cipher_parm_s *parm = opaque; + AssuanError rc; + + assuan_begin_confidential (parm->ctx); + rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen); + assuan_end_confidential (parm->ctx); + return rc; +} + + +/* Call the agent to do a decrypt operation using the key identified by + the hex string KEYGRIP. */ +int +gpgsm_agent_pkdecrypt (const char *keygrip, + KsbaConstSexp ciphertext, + char **r_buf, size_t *r_buflen ) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct cipher_parm_s cipher_parm; + size_t n, len; + char *buf, *endp; + size_t ciphertextlen; + + if (!keygrip || strlen(keygrip) != 40 || !ciphertext || !r_buf || !r_buflen) + return gpg_error (GPG_ERR_INV_VALUE); + *r_buf = NULL; + + ciphertextlen = gcry_sexp_canon_len (ciphertext, 0, NULL, NULL); + if (!ciphertextlen) + return gpg_error (GPG_ERR_INV_VALUE); + + rc = start_agent (); + if (rc) + return rc; + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + assert ( DIM(line) >= 50 ); + snprintf (line, DIM(line)-1, "SETKEY %s", keygrip); + line[DIM(line)-1] = 0; + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + init_membuf (&data, 1024); + cipher_parm.ctx = agent_ctx; + cipher_parm.ciphertext = ciphertext; + cipher_parm.ciphertextlen = ciphertextlen; + rc = assuan_transact (agent_ctx, "PKDECRYPT", + membuf_data_cb, &data, + inq_ciphertext_cb, &cipher_parm, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return map_assuan_err (rc); + } + + put_membuf (&data, "", 1); /* make sure it is 0 terminated */ + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error (GPG_ERR_ENOMEM); + /* FIXME: We would better a return a full S-exp and not just a part */ + assert (len); + len--; /* remove the terminating 0 */ + n = strtoul (buf, &endp, 10); + if (!n || *endp != ':') + return gpg_error (GPG_ERR_INV_SEXP); + endp++; + if (endp-buf+n > len) + return gpg_error (GPG_ERR_INV_SEXP); /* oops len does not + match internal len*/ + memmove (buf, endp, n); + *r_buflen = n; + *r_buf = buf; + return 0; +} + + + + + +/* Handle a KEYPARMS inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the end */ +static AssuanError +inq_genkey_parms (void *opaque, const char *keyword) +{ + struct genkey_parm_s *parm = opaque; + AssuanError rc; + + rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen); + return rc; +} + + + +/* Call the agent to generate a newkey */ +int +gpgsm_agent_genkey (KsbaConstSexp keyparms, KsbaSexp *r_pubkey) +{ + int rc; + struct genkey_parm_s gk_parm; + membuf_t data; + size_t len; + char *buf; + + *r_pubkey = NULL; + rc = start_agent (); + if (rc) + return rc; + + rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + init_membuf (&data, 1024); + gk_parm.ctx = agent_ctx; + gk_parm.sexp = keyparms; + gk_parm.sexplen = gcry_sexp_canon_len (keyparms, 0, NULL, NULL); + if (!gk_parm.sexplen) + return gpg_error (GPG_ERR_INV_VALUE); + rc = assuan_transact (agent_ctx, "GENKEY", + membuf_data_cb, &data, + inq_genkey_parms, &gk_parm, NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return map_assuan_err (rc); + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error (GPG_ERR_ENOMEM); + if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + *r_pubkey = buf; + return 0; +} + + +/* Ask the agent whether the certificate is in the list of trusted + keys */ +int +gpgsm_agent_istrusted (KsbaCert cert) +{ + int rc; + char *fpr; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (); + if (rc) + return rc; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + if (!fpr) + { + log_error ("error getting the fingerprint\n"); + return gpg_error (GPG_ERR_GENERAL); + } + + snprintf (line, DIM(line)-1, "ISTRUSTED %s", fpr); + line[DIM(line)-1] = 0; + xfree (fpr); + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return map_assuan_err (rc); +} + +/* Ask the agent to mark CERT as a trusted Root-CA one */ +int +gpgsm_agent_marktrusted (KsbaCert cert) +{ + int rc; + char *fpr, *dn; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (); + if (rc) + return rc; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + if (!fpr) + { + log_error ("error getting the fingerprint\n"); + return gpg_error (GPG_ERR_GENERAL); + } + + dn = ksba_cert_get_issuer (cert, 0); + if (!dn) + { + xfree (fpr); + return gpg_error (GPG_ERR_GENERAL); + } + snprintf (line, DIM(line)-1, "MARKTRUSTED %s S %s", fpr, dn); + line[DIM(line)-1] = 0; + ksba_free (dn); + xfree (fpr); + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return map_assuan_err (rc); +} + + + +/* Ask the agent whether the a corresponding secret key is available + for the given keygrip */ +int +gpgsm_agent_havekey (const char *hexkeygrip) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (); + if (rc) + return rc; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + snprintf (line, DIM(line)-1, "HAVEKEY %s", hexkeygrip); + line[DIM(line)-1] = 0; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return map_assuan_err (rc); +} + + +static AssuanError +learn_cb (void *opaque, const void *buffer, size_t length) +{ + struct learn_parm_s *parm = opaque; + size_t len; + char *buf; + KsbaCert cert; + int rc; + + if (parm->error) + return 0; + + if (buffer) + { + put_membuf (parm->data, buffer, length); + return 0; + } + /* END encountered - process what we have */ + buf = get_membuf (parm->data, &len); + if (!buf) + { + parm->error = gpg_error (GPG_ERR_ENOMEM); + return 0; + } + + + /* FIXME: this should go into import.c */ + cert = ksba_cert_new (); + if (!cert) + { + parm->error = gpg_error (GPG_ERR_ENOMEM); + return 0; + } + rc = ksba_cert_init_from_mem (cert, buf, len); + if (rc) + { + log_error ("failed to parse a certificate: %s\n", ksba_strerror (rc)); + ksba_cert_release (cert); + parm->error = map_ksba_err (rc); + return 0; + } + + rc = gpgsm_basic_cert_check (cert); + if (gpg_err_code (rc) == GPG_ERR_MISSING_CERT) + { /* For later use we store it in the ephemeral database. */ + log_info ("issuer certificate missing - storing as ephemeral\n"); + keydb_store_cert (cert, 1, NULL); + } + else if (rc) + log_error ("invalid certificate: %s\n", gpg_strerror (rc)); + else + { + int existed; + + if (!keydb_store_cert (cert, 0, &existed)) + { + if (opt.verbose > 1 && existed) + log_info ("certificate already in DB\n"); + else if (opt.verbose && !existed) + log_info ("certificate imported\n"); + } + } + + ksba_cert_release (cert); + init_membuf (parm->data, 4096); + return 0; +} + +/* Call the agent to learn about a smartcard */ +int +gpgsm_agent_learn () +{ + int rc; + struct learn_parm_s learn_parm; + membuf_t data; + size_t len; + + rc = start_agent (); + if (rc) + return rc; + + init_membuf (&data, 4096); + learn_parm.error = 0; + learn_parm.ctx = agent_ctx; + learn_parm.data = &data; + rc = assuan_transact (agent_ctx, "LEARN --send", + learn_cb, &learn_parm, + NULL, NULL, NULL, NULL); + xfree (get_membuf (&data, &len)); + if (rc) + return map_assuan_err (rc); + return learn_parm.error; +} + + +/* Ask the agent to change the passphrase of the key identified by HEXKEYGRIP. */ +int +gpgsm_agent_passwd (const char *hexkeygrip) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + rc = start_agent (); + if (rc) + return rc; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + snprintf (line, DIM(line)-1, "PASSWD %s", hexkeygrip); + line[DIM(line)-1] = 0; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return map_assuan_err (rc); +} + diff --git a/sm/call-dirmngr.c b/sm/call-dirmngr.c new file mode 100644 index 000000000..b182b246c --- /dev/null +++ b/sm/call-dirmngr.c @@ -0,0 +1,632 @@ +/* call-dirmngr.c - communication with the dromngr + * Copyright (C) 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> +#include <ctype.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <assuan.h> + +#include "i18n.h" + +struct membuf { + size_t len; + size_t size; + char *buf; + int out_of_core; +}; + + + +static ASSUAN_CONTEXT dirmngr_ctx = NULL; +static int force_pipe_server = 0; + +struct inq_certificate_parm_s { + ASSUAN_CONTEXT ctx; + KsbaCert cert; +}; + +struct lookup_parm_s { + CTRL ctrl; + ASSUAN_CONTEXT ctx; + void (*cb)(void *, KsbaCert); + void *cb_value; + struct membuf data; + int error; +}; + +struct run_command_parm_s { + ASSUAN_CONTEXT ctx; +}; + + +/* A simple implementation of a dynamic buffer. Use init_membuf() to + create a buffer, put_membuf to append bytes and get_membuf to + release and return the buffer. Allocation errors are detected but + only returned at the final get_membuf(), this helps not to clutter + the code with out of core checks. */ + +static void +init_membuf (struct membuf *mb, int initiallen) +{ + mb->len = 0; + mb->size = initiallen; + mb->out_of_core = 0; + mb->buf = xtrymalloc (initiallen); + if (!mb->buf) + mb->out_of_core = 1; +} + +static void +put_membuf (struct membuf *mb, const void *buf, size_t len) +{ + if (mb->out_of_core) + return; + + if (mb->len + len >= mb->size) + { + char *p; + + mb->size += len + 1024; + p = xtryrealloc (mb->buf, mb->size); + if (!p) + { + mb->out_of_core = 1; + return; + } + mb->buf = p; + } + memcpy (mb->buf + mb->len, buf, len); + mb->len += len; +} + +static void * +get_membuf (struct membuf *mb, size_t *len) +{ + char *p; + + if (mb->out_of_core) + { + xfree (mb->buf); + mb->buf = NULL; + return NULL; + } + + p = mb->buf; + *len = mb->len; + mb->buf = NULL; + mb->out_of_core = 1; /* don't allow a reuse */ + return p; +} + + + + + +/* Try to connect to the agent via socket or fork it off and work by + pipes. Handle the server's initial greeting */ +static int +start_dirmngr (void) +{ + int rc; + char *infostr, *p; + ASSUAN_CONTEXT ctx; + + if (dirmngr_ctx) + return 0; /* fixme: We need a context for each thread or serialize + the access to the dirmngr */ + + infostr = force_pipe_server? NULL : getenv ("DIRMNGR_INFO"); + if (!infostr) + { + const char *pgmname; + const char *argv[3]; + int no_close_list[3]; + int i; + + if (opt.verbose) + log_info (_("no running dirmngr - starting one\n")); + + if (fflush (NULL)) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("error flushing pending output: %s\n", strerror (errno)); + return tmperr; + } + + if (!opt.dirmngr_program || !*opt.dirmngr_program) + opt.dirmngr_program = GNUPG_DEFAULT_DIRMNGR; + if ( !(pgmname = strrchr (opt.dirmngr_program, '/'))) + pgmname = opt.dirmngr_program; + else + pgmname++; + + argv[0] = pgmname; + argv[1] = "--server"; + argv[2] = NULL; + + i=0; + if (log_get_fd () != -1) + no_close_list[i++] = log_get_fd (); + no_close_list[i++] = fileno (stderr); + no_close_list[i] = -1; + + /* connect to the agent and perform initial handshaking */ + rc = assuan_pipe_connect (&ctx, opt.dirmngr_program, (char**)argv, + no_close_list); + } + else + { + int prot; + int pid; + + infostr = xstrdup (infostr); + if ( !(p = strchr (infostr, ':')) || p == infostr) + { + log_error (_("malformed DIRMNGR_INFO environment variable\n")); + xfree (infostr); + force_pipe_server = 1; + return start_dirmngr (); + } + *p++ = 0; + pid = atoi (p); + while (*p && *p != ':') + p++; + prot = *p? atoi (p+1) : 0; + if (prot != 1) + { + log_error (_("dirmngr protocol version %d is not supported\n"), + prot); + xfree (infostr); + force_pipe_server = 1; + return start_dirmngr (); + } + + rc = assuan_socket_connect (&ctx, infostr, pid); + xfree (infostr); + if (rc == ASSUAN_Connect_Failed) + { + log_error (_("can't connect to the dirmngr - trying fall back\n")); + force_pipe_server = 1; + return start_dirmngr (); + } + } + + if (rc) + { + log_error ("can't connect to the dirmngr: %s\n", assuan_strerror (rc)); + return gpg_error (GPG_ERR_NO_DIRMNGR); + } + dirmngr_ctx = ctx; + + if (DBG_ASSUAN) + log_debug ("connection to dirmngr established\n"); + return 0; +} + + + +/* Handle a SENDCERT inquiry. */ +static AssuanError +inq_certificate (void *opaque, const char *line) +{ + struct inq_certificate_parm_s *parm = opaque; + AssuanError rc; + const unsigned char *der; + size_t derlen; + + if (!(!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]))) + { + log_error ("unsupported inquiry `%s'\n", line); + return ASSUAN_Inquire_Unknown; + } + line += 8; + + if (!*line) + { /* send the current certificate */ + der = ksba_cert_get_image (parm->cert, &derlen); + if (!der) + rc = ASSUAN_Inquire_Error; + else + rc = assuan_send_data (parm->ctx, der, derlen); + } + else + { /* send the given certificate */ + int err; + KsbaCert cert; + + err = gpgsm_find_cert (line, &cert); + if (err) + { + log_error ("certificate not found: %s\n", gpg_strerror (err)); + rc = ASSUAN_Inquire_Error; + } + else + { + der = ksba_cert_get_image (cert, &derlen); + if (!der) + rc = ASSUAN_Inquire_Error; + else + rc = assuan_send_data (parm->ctx, der, derlen); + ksba_cert_release (cert); + } + } + + return rc; +} + + + +/* Call the directory manager to check whether the certificate is valid + Returns 0 for valid or usually one of the errors: + + GPG_ERR_CERTIFICATE_REVOKED + GPG_ERR_NO_CRL_KNOWN + GPG_ERR_CRL_TOO_OLD + */ +int +gpgsm_dirmngr_isvalid (KsbaCert cert) +{ + int rc; + char *certid; + char line[ASSUAN_LINELENGTH]; + struct inq_certificate_parm_s parm; + + rc = start_dirmngr (); + if (rc) + return rc; + + certid = gpgsm_get_certid (cert); + if (!certid) + { + log_error ("error getting the certificate ID\n"); + return gpg_error (GPG_ERR_GENERAL); + } + + if (opt.verbose > 1) + { + char *fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1); + log_info ("asking dirmngr about %s\n", fpr); + xfree (fpr); + } + + parm.ctx = dirmngr_ctx; + parm.cert = cert; + + snprintf (line, DIM(line)-1, "ISVALID %s", certid); + line[DIM(line)-1] = 0; + xfree (certid); + + rc = assuan_transact (dirmngr_ctx, line, NULL, NULL, + inq_certificate, &parm, NULL, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay"); + return map_assuan_err (rc); +} + + + +/* Lookup helpers*/ +static AssuanError +lookup_cb (void *opaque, const void *buffer, size_t length) +{ + struct lookup_parm_s *parm = opaque; + size_t len; + char *buf; + KsbaCert cert; + int rc; + + if (parm->error) + return 0; + + if (buffer) + { + put_membuf (&parm->data, buffer, length); + return 0; + } + /* END encountered - process what we have */ + buf = get_membuf (&parm->data, &len); + if (!buf) + { + parm->error = gpg_error (GPG_ERR_ENOMEM); + return 0; + } + + cert = ksba_cert_new (); + if (!cert) + { + parm->error = gpg_error (GPG_ERR_ENOMEM); + return 0; + } + rc = ksba_cert_init_from_mem (cert, buf, len); + if (rc) + { + log_error ("failed to parse a certificate: %s\n", ksba_strerror (rc)); + } + else + { + parm->cb (parm->cb_value, cert); + } + + ksba_cert_release (cert); + init_membuf (&parm->data, 4096); + return 0; +} + +/* Return a properly escaped pattern from NAMES. The only error + return is NULL to indicate a malloc failure. */ +static char * +pattern_from_strlist (STRLIST names) +{ + STRLIST sl; + int n; + const char *s; + char *pattern, *p; + + for (n=0, sl=names; sl; sl = sl->next) + { + for (s=sl->d; *s; s++, n++) + { + if (*s == '%' || *s == ' ' || *s == '+') + n += 2; + } + n++; + } + + p = pattern = xtrymalloc (n+1); + if (!pattern) + return NULL; + + for (n=0, sl=names; sl; sl = sl->next) + { + for (s=sl->d; *s; s++) + { + switch (*s) + { + case '%': + *p++ = '%'; + *p++ = '2'; + *p++ = '5'; + break; + case ' ': + *p++ = '%'; + *p++ = '2'; + *p++ = '0'; + break; + case '+': + *p++ = '%'; + *p++ = '2'; + *p++ = 'B'; + break; + default: + *p++ = *s; + break; + } + } + *p++ = ' '; + } + if (p == pattern) + *pattern = 0; /* is empty */ + else + p[-1] = '\0'; /* remove trailing blank */ + + return pattern; +} + +static AssuanError +lookup_status_cb (void *opaque, const char *line) +{ + struct lookup_parm_s *parm = opaque; + + if (!strncmp (line, "TRUNCATED", 9) && (line[9]==' ' || !line[9])) + { + if (parm->ctrl) + { + for (line +=9; *line == ' '; line++) + ; + gpgsm_status (parm->ctrl, STATUS_TRUNCATED, line); + } + } + return 0; +} + + +/* Run the Directroy Managers lookup command using the pattern + compiled from the strings given in NAMES. The caller must provide + the callback CB which will be passed cert by cert. Note that CTRL + is optional. */ +int +gpgsm_dirmngr_lookup (CTRL ctrl, STRLIST names, + void (*cb)(void*, KsbaCert), void *cb_value) +{ + int rc; + char *pattern; + char line[ASSUAN_LINELENGTH]; + struct lookup_parm_s parm; + size_t len; + + rc = start_dirmngr (); + if (rc) + return rc; + + pattern = pattern_from_strlist (names); + if (!pattern) + return OUT_OF_CORE (errno); + snprintf (line, DIM(line)-1, "LOOKUP %s", pattern); + line[DIM(line)-1] = 0; + xfree (pattern); + + parm.ctrl = ctrl; + parm.ctx = dirmngr_ctx; + parm.cb = cb; + parm.cb_value = cb_value; + parm.error = 0; + init_membuf (&parm.data, 4096); + + rc = assuan_transact (dirmngr_ctx, line, lookup_cb, &parm, + NULL, NULL, lookup_status_cb, &parm); + xfree (get_membuf (&parm.data, &len)); + if (rc) + return map_assuan_err (rc); + return parm.error; +} + + + +/* Run Command helpers*/ + +/* Fairly simple callback to write all output of dirmngr to stdout. */ +static AssuanError +run_command_cb (void *opaque, const void *buffer, size_t length) +{ + if (buffer) + { + if ( fwrite (buffer, length, 1, stdout) != 1 ) + log_error ("error writing to stdout: %s\n", strerror (errno)); + } + return 0; +} + +/* Handle inquiries from the dirmngr COMMAND. */ +static AssuanError +run_command_inq_cb (void *opaque, const char *line) +{ + struct run_command_parm_s *parm = opaque; + AssuanError rc = 0; + + if ( !strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]) ) + { /* send the given certificate */ + int err; + KsbaCert cert; + const unsigned char *der; + size_t derlen; + + line += 8; + if (!*line) + return ASSUAN_Inquire_Error; + + err = gpgsm_find_cert (line, &cert); + if (err) + { + log_error ("certificate not found: %s\n", gpg_strerror (err)); + rc = ASSUAN_Inquire_Error; + } + else + { + der = ksba_cert_get_image (cert, &derlen); + if (!der) + rc = ASSUAN_Inquire_Error; + else + rc = assuan_send_data (parm->ctx, der, derlen); + ksba_cert_release (cert); + } + } + else if ( !strncmp (line, "PRINTINFO", 9) && (line[9] == ' ' || !line[9]) ) + { /* Simply show the message given in the argument. */ + line += 9; + log_info ("dirmngr: %s\n", line); + } + else + { + log_error ("unsupported inquiry `%s'\n", line); + rc = ASSUAN_Inquire_Unknown; + } + + return rc; +} + +static AssuanError +run_command_status_cb (void *opaque, const char *line) +{ + if (opt.verbose) + { + log_info ("dirmngr status: %s\n", line); + } + return 0; +} + + + +/* Pass COMMAND to dirmngr and print all output generated by Dirmngr + to stdout. A couple of inquiries are defined (see above). ARGC + arguments in ARGV are given to the Dirmngr. Spaces, plus and + percent characters within the argument strings are percent escaped + so that blanks can act as delimiters. */ +int +gpgsm_dirmngr_run_command (CTRL ctrl, const char *command, + int argc, char **argv) +{ + int rc; + int i; + const char *s; + char *line, *p; + size_t len; + struct run_command_parm_s parm; + + rc = start_dirmngr (); + if (rc) + return rc; + + parm.ctx = dirmngr_ctx; + + len = strlen (command) + 1; + for (i=0; i < argc; i++) + len += 1 + 3*strlen (argv[i]); /* enough space for percent escaping */ + line = xtrymalloc (len); + if (!line) + return OUT_OF_CORE (errno); + + p = stpcpy (line, command); + for (i=0; i < argc; i++) + { + *p++ = ' '; + for (s=argv[i]; *s; s++) + { + if (!isascii (*s)) + *p++ = *s; + else if (*s == ' ') + *p++ = '+'; + else if (!isprint (*s) || *s == '+') + { + sprintf (p, "%%%02X", *s); + p += 3; + } + else + *p++ = *s; + } + } + *p = 0; + + rc = assuan_transact (dirmngr_ctx, line, + run_command_cb, NULL, + run_command_inq_cb, &parm, + run_command_status_cb, NULL); + xfree (line); + log_info ("response of dirmngr: %s\n", rc? assuan_strerror (rc): "okay"); + return map_assuan_err (rc); +} diff --git a/sm/certchain.c b/sm/certchain.c new file mode 100644 index 000000000..6323c725e --- /dev/null +++ b/sm/certchain.c @@ -0,0 +1,793 @@ +/* certchain.c - certificate chain validation + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + +static int +unknown_criticals (KsbaCert cert) +{ + static const char *known[] = { + "2.5.29.15", /* keyUsage */ + "2.5.29.19", /* basic Constraints */ + "2.5.29.32", /* certificatePolicies */ + NULL + }; + int rc = 0, i, idx, crit; + const char *oid; + KsbaError err; + + 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++) + ; + if (!known[i]) + { + log_error (_("critical certificate extension %s is not supported\n"), + oid); + rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT); + } + } + if (err && err != -1) + rc = map_ksba_err (err); + + return rc; +} + +static int +allowed_ca (KsbaCert cert, int *chainlen) +{ + KsbaError err; + int flag; + + err = ksba_cert_is_ca (cert, &flag, chainlen); + if (err) + return map_ksba_err (err); + if (!flag) + { + log_error (_("issuer certificate is not marked as a CA\n")); + return gpg_error (GPG_ERR_BAD_CA_CERT); + } + return 0; +} + + +static int +check_cert_policy (KsbaCert cert) +{ + KsbaError err; + char *policies; + FILE *fp; + int any_critical; + + err = ksba_cert_get_cert_policies (cert, &policies); + if (err == KSBA_No_Data) + return 0; /* no policy given */ + if (err) + return map_ksba_err (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"); + + if (!opt.policy_file) + { + xfree (policies); + if (any_critical) + { + log_error ("critical marked policy without configured policies\n"); + return gpg_error (GPG_ERR_NO_POLICY_MATCH); + } + return 0; + } + + fp = fopen (opt.policy_file, "r"); + if (!fp) + { + log_error ("failed to open `%s': %s\n", + opt.policy_file, strerror (errno)); + xfree (policies); + return gpg_error (GPG_ERR_NO_POLICY_MATCH); + } + + for (;;) + { + int c; + char *p, line[256]; + char *haystack, *allowed; + + /* read line */ + do + { + if (!fgets (line, DIM(line)-1, fp) ) + { + gpg_error_t tmperr; + + xfree (policies); + if (feof (fp)) + { + fclose (fp); + /* with no critical policies this is only a warning */ + if (!any_critical) + { + log_info (_("note: certificate policy not allowed\n")); + return 0; + } + log_error (_("certificate policy not allowed\n")); + return gpg_error (GPG_ERR_NO_POLICY_MATCH); + } + tmperr = gpg_error (gpg_err_code_from_errno (errno)); + fclose (fp); + return tmperr; + } + + if (!*line || line[strlen(line)-1] != '\n') + { + /* eat until end of line */ + while ( (c=getc (fp)) != EOF && c != '\n') + ; + fclose (fp); + xfree (policies); + return gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + } + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + } + while (!*p || *p == '\n' || *p == '#'); + + /* parse line */ + for (allowed=line; spacep (allowed); allowed++) + ; + p = strpbrk (allowed, " :\n"); + if (!*p || p == allowed) + { + fclose (fp); + xfree (policies); + return gpg_error (GPG_ERR_CONFIGURATION); + } + *p = 0; /* strip the rest of the line */ + /* See whether we find ALLOWED (which is an OID) in POLICIES */ + for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1) + { + if ( !(p == policies || p[-1] == '\n') ) + continue; /* does not match the begin of a line */ + if (p[strlen (allowed)] != ':') + continue; /* the length does not match */ + /* Yep - it does match so return okay */ + fclose (fp); + xfree (policies); + return 0; + } + } +} + + +static void +find_up_store_certs_cb (void *cb_value, KsbaCert cert) +{ + if (keydb_store_cert (cert, 1, NULL)) + log_error ("error storing issuer certificate as ephemeral\n"); + ++*(int*)cb_value; +} + + +static int +find_up (KEYDB_HANDLE kh, KsbaCert cert, const char *issuer) +{ + KsbaName authid; + KsbaSexp authidno; + int rc = -1; + + if (!ksba_cert_get_auth_key_id (cert, NULL, &authid, &authidno)) + { + const char *s = ksba_name_enum (authid, 0); + if (s && *authidno) + { + rc = keydb_search_issuer_sn (kh, s, authidno); + if (rc) + keydb_search_reset (kh); + if (rc == -1) + { /* And try the ephemeral DB. */ + int old = keydb_set_ephemeral (kh, 1); + if (!old) + { + rc = keydb_search_issuer_sn (kh, s, authidno); + if (rc) + keydb_search_reset (kh); + } + keydb_set_ephemeral (kh, old); + } + } + /* print a note so that the user does not feel too helpless when + an issuer certificate was found and gpgsm prints BAD + signature becuase it is not the correct one. */ + if (rc == -1) + { + log_info ("issuer certificate (#"); + gpgsm_dump_serial (authidno); + log_printf ("/"); + gpgsm_dump_string (s); + log_printf (") not found\n"); + } + else if (rc) + log_error ("failed to find authorityKeyIdentifier: rc=%d\n", rc); + ksba_name_release (authid); + xfree (authidno); + /* Fixme: don't know how to do dirmngr lookup with serial+issuer. */ + } + + if (rc) /* not found via authorithyKeyIdentifier, try regular issuer name */ + rc = keydb_search_subject (kh, issuer); + if (rc == -1) + { + /* Not found, lets see whether we have one in the ephemeral key DB. */ + int old = keydb_set_ephemeral (kh, 1); + if (!old) + { + keydb_search_reset (kh); + rc = keydb_search_subject (kh, issuer); + } + keydb_set_ephemeral (kh, old); + } + + if (rc == -1 && opt.auto_issuer_key_retrieve) + { + STRLIST names = NULL; + int count = 0; + char *pattern; + const char *s; + + if (opt.verbose) + log_info (_("looking up issuer at external location\n")); + /* dirmngr is confused about unknown attributes so has a quick + and ugly hack we locate the CN and use this and the + following. Fixme: we should have far better parsing in the + dirmngr. */ + s = strstr (issuer, "CN="); + if (!s || s == issuer || s[-1] != ',') + s = issuer; + + pattern = xtrymalloc (strlen (s)+2); + if (!pattern) + return OUT_OF_CORE (errno); + strcpy (stpcpy (pattern, "/"), s); + add_to_strlist (&names, pattern); + xfree (pattern); + rc = gpgsm_dirmngr_lookup (NULL, names, find_up_store_certs_cb, &count); + free_strlist (names); + if (opt.verbose) + log_info (_("number of issuers matching: %d\n"), count); + if (rc) + { + log_error ("external key lookup failed: %s\n", gpg_strerror (rc)); + rc = -1; + } + else if (!count) + rc = -1; + else + { + int old; + /* The issuers are currently stored in the ephemeral key + DB, so we temporary switch to ephemeral mode. */ + old = keydb_set_ephemeral (kh, 1); + keydb_search_reset (kh); + rc = keydb_search_subject (kh, issuer); + keydb_set_ephemeral (kh, old); + } + } + return rc; +} + + +/* Return the next certificate up in the chain starting at START. + Returns -1 when there are no more certificates. */ +int +gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next) +{ + int rc = 0; + char *issuer = NULL; + char *subject = NULL; + KEYDB_HANDLE kh = keydb_new (0); + + *r_next = NULL; + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + issuer = ksba_cert_get_issuer (start, 0); + subject = ksba_cert_get_subject (start, 0); + if (!issuer) + { + log_error ("no issuer found in certificate\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + if (!subject) + { + log_error ("no subject found in certificate\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + if (!strcmp (issuer, subject)) + { + rc = -1; /* we are at the root */ + goto leave; + } + + rc = find_up (kh, start, issuer); + if (rc) + { + /* it is quite common not to have a certificate, so better don't + print an error here */ + if (rc != -1 && opt.verbose > 1) + log_error ("failed to find issuer's certificate: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_MISSING_CERT); + goto leave; + } + + rc = keydb_get_cert (kh, r_next); + if (rc) + { + log_error ("failed to get cert: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_GENERAL); + } + + leave: + xfree (issuer); + xfree (subject); + keydb_release (kh); + return rc; +} + + +/* Check whether the CERT is a root certificate. Returns True if this + is the case. */ +int +gpgsm_is_root_cert (KsbaCert cert) +{ + char *issuer; + char *subject; + int yes; + + issuer = ksba_cert_get_issuer (cert, 0); + subject = ksba_cert_get_subject (cert, 0); + yes = (issuer && subject && !strcmp (issuer, subject)); + xfree (issuer); + xfree (subject); + return yes; +} + + +/* Validate a chain and optionally return the nearest expiration time + in R_EXPTIME */ +int +gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime) +{ + int rc = 0, depth = 0, maxdepth; + char *issuer = NULL; + char *subject = NULL; + KEYDB_HANDLE kh = keydb_new (0); + KsbaCert subject_cert = NULL, issuer_cert = NULL; + time_t current_time = gnupg_get_time (); + time_t exptime = 0; + int any_expired = 0; + int any_revoked = 0; + int any_no_crl = 0; + int any_crl_too_old = 0; + int any_no_policy_match = 0; + + if (r_exptime) + *r_exptime = 0; + + if (opt.no_chain_validation) + { + log_info ("WARNING: bypassing certificate chain validation\n"); + return 0; + } + + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + if (DBG_X509) + gpgsm_dump_cert ("subject", cert); + + subject_cert = cert; + maxdepth = 50; + + for (;;) + { + xfree (issuer); + xfree (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"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + { + time_t not_before, not_after; + + not_before = ksba_cert_get_validity (subject_cert, 0); + not_after = ksba_cert_get_validity (subject_cert, 1); + if (not_before == (time_t)(-1) || not_after == (time_t)(-1)) + { + log_error ("certificate with invalid validity\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + if (not_after) + { + if (!exptime) + exptime = not_after; + else if (not_after < exptime) + exptime = not_after; + } + + if (not_before && current_time < not_before) + { + log_error ("certificate too young; valid from "); + gpgsm_dump_time (not_before); + log_printf ("\n"); + rc = gpg_error (GPG_ERR_CERT_TOO_YOUNG); + goto leave; + } + if (not_after && current_time > not_after) + { + log_error ("certificate has expired at "); + gpgsm_dump_time (not_after); + log_printf ("\n"); + any_expired = 1; + } + } + + rc = unknown_criticals (subject_cert); + if (rc) + goto leave; + + if (!opt.no_policy_check) + { + rc = check_cert_policy (subject_cert); + if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH) + { + any_no_policy_match = 1; + rc = 1; + } + else if (rc) + goto leave; + } + + if (!opt.no_crl_check) + { + rc = gpgsm_dirmngr_isvalid (subject_cert); + if (rc) + { + switch (rc) + { + case GPG_ERR_CERT_REVOKED: + log_error (_("the certificate has been revoked\n")); + any_revoked = 1; + break; + case GPG_ERR_NO_CRL_KNOWN: + log_error (_("no CRL found for certificate\n")); + any_no_crl = 1; + break; + case GPG_ERR_CRL_TOO_OLD: + log_error (_("the available CRL is too old\n")); + log_info (_("please make sure that the " + "\"dirmngr\" is properly installed\n")); + any_crl_too_old = 1; + break; + default: + log_error (_("checking the CRL failed: %s\n"), + gpg_strerror (rc)); + goto leave; + } + rc = 0; + } + } + + if (subject && !strcmp (issuer, subject)) + { + if (gpgsm_check_cert_sig (subject_cert, subject_cert) ) + { + log_error ("selfsigned certificate has a BAD signatures\n"); + rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN + : GPG_ERR_BAD_CERT); + goto leave; + } + rc = allowed_ca (subject_cert, NULL); + if (rc) + goto leave; + + rc = gpgsm_agent_istrusted (subject_cert); + if (!rc) + ; + else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) + { + int rc2; + + char *fpr = gpgsm_get_fingerprint_string (subject_cert, + GCRY_MD_SHA1); + log_info (_("root certificate is not marked trusted\n")); + log_info (_("fingerprint=%s\n"), fpr? fpr : "?"); + xfree (fpr); + rc2 = gpgsm_agent_marktrusted (subject_cert); + if (!rc2) + { + log_info (_("root certificate has now" + " been marked as trusted\n")); + rc = 0; + } + else + { + gpgsm_dump_cert ("issuer", subject_cert); + log_info ("after checking the fingerprint, you may want " + "to enter it manually into " + "\"~/.gnupg-test/trustlist.txt\"\n"); + } + } + else + { + log_error (_("checking the trust list failed: %s\n"), + gpg_strerror (rc)); + } + + break; /* okay, a self-signed certicate is an end-point */ + } + + depth++; + if (depth > maxdepth) + { + log_error (_("certificate chain too long\n")); + rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); + goto leave; + } + + /* find the next cert up the tree */ + keydb_search_reset (kh); + rc = find_up (kh, subject_cert, issuer); + if (rc) + { + if (rc == -1) + { + log_info ("issuer certificate (#/"); + gpgsm_dump_string (issuer); + log_printf (") not found\n"); + } + else + log_error ("failed to find issuer's certificate: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_MISSING_CERT); + goto leave; + } + + ksba_cert_release (issuer_cert); issuer_cert = NULL; + rc = keydb_get_cert (kh, &issuer_cert); + if (rc) + { + log_error ("failed to get cert: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + if (DBG_X509) + { + log_debug ("got issuer's certificate:\n"); + gpgsm_dump_cert ("issuer", issuer_cert); + } + + if (gpgsm_check_cert_sig (issuer_cert, subject_cert) ) + { + log_error ("certificate has a BAD signatures\n"); + rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); + goto leave; + } + + { + int chainlen; + rc = allowed_ca (issuer_cert, &chainlen); + if (rc) + goto leave; + if (chainlen >= 0 && (depth - 1) > chainlen) + { + log_error (_("certificate chain longer than allowed by CA (%d)\n"), + chainlen); + rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN); + goto leave; + } + } + + rc = gpgsm_cert_use_cert_p (issuer_cert); + if (rc) + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + gpgsm_status2 (ctrl, STATUS_ERROR, "certcert.issuer.keyusage", + numbuf, NULL); + rc = 0; + } + + if (opt.verbose) + log_info ("certificate is good\n"); + + keydb_search_reset (kh); + subject_cert = issuer_cert; + issuer_cert = NULL; + } + + if (opt.no_policy_check) + log_info ("policies not checked due to --disable-policy-checks option\n"); + if (opt.no_crl_check) + log_info ("CRLs not checked due to --disable-crl-checks option\n"); + + if (!rc) + { /* If we encountered an error somewhere during the checks, set + the error code to the most critical one */ + if (any_revoked) + rc = gpg_error (GPG_ERR_CERT_REVOKED); + else if (any_no_crl) + rc = gpg_error (GPG_ERR_NO_CRL_KNOWN); + else if (any_crl_too_old) + rc = gpg_error (GPG_ERR_CRL_TOO_OLD); + else if (any_no_policy_match) + rc = gpg_error (GPG_ERR_NO_POLICY_MATCH); + else if (any_expired) + rc = gpg_error (GPG_ERR_CERT_EXPIRED); + } + + leave: + if (r_exptime) + *r_exptime = exptime; + xfree (issuer); + keydb_release (kh); + ksba_cert_release (issuer_cert); + if (subject_cert != cert) + ksba_cert_release (subject_cert); + return rc; +} + + +/* Check that the given certificate is valid but DO NOT check any + constraints. We assume that the issuers certificate is already in + the DB and that this one is valid; which it should be because it + has been checked using this function. */ +int +gpgsm_basic_cert_check (KsbaCert cert) +{ + int rc = 0; + char *issuer = NULL; + char *subject = NULL; + KEYDB_HANDLE kh = keydb_new (0); + KsbaCert issuer_cert = NULL; + + if (opt.no_chain_validation) + { + log_info ("WARNING: bypassing basic certificate checks\n"); + return 0; + } + + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + issuer = ksba_cert_get_issuer (cert, 0); + subject = ksba_cert_get_subject (cert, 0); + if (!issuer) + { + log_error ("no issuer found in certificate\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + + if (subject && !strcmp (issuer, subject)) + { + if (gpgsm_check_cert_sig (cert, cert) ) + { + log_error ("selfsigned certificate has a BAD signatures\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + } + else + { + /* find the next cert up the tree */ + keydb_search_reset (kh); + rc = find_up (kh, cert, issuer); + if (rc) + { + if (rc == -1) + { + log_info ("issuer certificate (#/"); + gpgsm_dump_string (issuer); + log_printf (") not found\n"); + } + else + log_error ("failed to find issuer's certificate: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_MISSING_CERT); + goto leave; + } + + ksba_cert_release (issuer_cert); issuer_cert = NULL; + rc = keydb_get_cert (kh, &issuer_cert); + if (rc) + { + log_error ("failed to get cert: rc=%d\n", rc); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + if (gpgsm_check_cert_sig (issuer_cert, cert) ) + { + log_error ("certificate has a BAD signatures\n"); + rc = gpg_error (GPG_ERR_BAD_CERT); + goto leave; + } + if (opt.verbose) + log_info ("certificate is good\n"); + } + + leave: + xfree (issuer); + keydb_release (kh); + ksba_cert_release (issuer_cert); + return rc; +} + diff --git a/sm/certcheck.c b/sm/certcheck.c new file mode 100644 index 000000000..35509c67e --- /dev/null +++ b/sm/certcheck.c @@ -0,0 +1,300 @@ +/* certcheck.c - check one certificate + * Copyright (C) 2001, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +static int +do_encode_md (gcry_md_hd_t md, int algo, unsigned int nbits, + gcry_mpi_t *r_val) +{ + int nframe = (nbits+7) / 8; + byte *frame; + int i, n; + byte asn[100]; + size_t asnlen; + size_t len; + + asnlen = DIM(asn); + if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen)) + { + log_error ("No object identifier for algo %d\n", algo); + return gpg_error (GPG_ERR_INTERNAL); + } + + len = gcry_md_get_algo_dlen (algo); + + if ( len + asnlen + 4 > nframe ) + { + log_error ("can't encode a %d bit MD into a %d bits frame\n", + (int)(len*8), (int)nbits); + return gpg_error (GPG_ERR_INTERNAL); + } + + /* We encode the MD in this way: + * + * 0 A PAD(n bytes) 0 ASN(asnlen bytes) MD(len bytes) + * + * PAD consists of FF bytes. + */ + frame = xtrymalloc (nframe); + if (!frame) + return OUT_OF_CORE (errno); + n = 0; + frame[n++] = 0; + frame[n++] = 1; /* block type */ + i = nframe - len - asnlen -3 ; + assert ( i > 1 ); + memset ( frame+n, 0xff, i ); n += i; + frame[n++] = 0; + memcpy ( frame+n, asn, asnlen ); n += asnlen; + memcpy ( frame+n, gcry_md_read(md, algo), len ); n += len; + assert ( n == nframe ); + if (DBG_X509) + { + int j; + log_debug ("encoded hash:"); + for (j=0; j < nframe; j++) + log_printf (" %02X", frame[j]); + log_printf ("\n"); + } + + gcry_mpi_scan (r_val, GCRYMPI_FMT_USG, frame, n, &nframe); + xfree (frame); + return 0; +} + + +/* + 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. */ +int +gpgsm_check_cert_sig (KsbaCert issuer_cert, KsbaCert cert) +{ + const char *algoid; + gcry_md_hd_t md; + int rc, algo; + gcry_mpi_t frame; + KsbaSexp p; + size_t n; + gcry_sexp_t s_sig, s_hash, s_pkey; + + algo = gcry_md_map_name ( (algoid=ksba_cert_get_digest_algo (cert))); + if (!algo) + { + log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); + return gpg_error (GPG_ERR_GENERAL); + } + rc = gcry_md_open (&md, algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + return rc; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "hash.cert"); + + rc = ksba_cert_hash (cert, 1, HASH_FNC, md); + if (rc) + { + log_error ("ksba_cert_hash failed: %s\n", ksba_strerror (rc)); + gcry_md_close (md); + return map_ksba_err (rc); + } + gcry_md_final (md); + + 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"); + gcry_md_close (md); + ksba_free (p); + return gpg_error (GPG_ERR_BUG); + } + if (DBG_X509) + { + int j; + log_debug ("signature value:"); + for (j=0; j < n; j++) + log_printf (" %02X", p[j]); + log_printf ("\n"); + } + + rc = gcry_sexp_sscan ( &s_sig, NULL, p, n); + ksba_free (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + return rc; + } + + 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); + } + rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + ksba_free (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + gcry_sexp_release (s_sig); + return rc; + } + + rc = do_encode_md (md, algo, gcry_pk_get_nbits (s_pkey), &frame); + if (rc) + { + gcry_md_close (md); + gcry_sexp_release (s_sig); + gcry_sexp_release (s_pkey); + return rc; + } + + /* put hash into the S-Exp s_hash */ + if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) ) + BUG (); + gcry_mpi_release (frame); + + + rc = gcry_pk_verify (s_sig, s_hash, s_pkey); + if (DBG_CRYPTO) + log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc)); + gcry_md_close (md); + gcry_sexp_release (s_sig); + gcry_sexp_release (s_hash); + gcry_sexp_release (s_pkey); + return rc; +} + + + +int +gpgsm_check_cms_signature (KsbaCert cert, KsbaConstSexp sigval, + gcry_md_hd_t md, int algo) +{ + int rc; + KsbaSexp p; + gcry_mpi_t frame; + gcry_sexp_t s_sig, s_hash, s_pkey; + size_t n; + + n = gcry_sexp_canon_len (sigval, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + return gpg_error (GPG_ERR_BUG); + } + rc = gcry_sexp_sscan (&s_sig, NULL, sigval, n); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + return rc; + } + + p = ksba_cert_get_public_key (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); + gcry_sexp_release (s_sig); + return gpg_error (GPG_ERR_BUG); + } + if (DBG_X509) + log_printhex ("public key: ", p, n); + + rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + ksba_free (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + gcry_sexp_release (s_sig); + return rc; + } + + + rc = do_encode_md (md, algo, gcry_pk_get_nbits (s_pkey), &frame); + if (rc) + { + gcry_sexp_release (s_sig); + gcry_sexp_release (s_pkey); + return rc; + } + /* put hash into the S-Exp s_hash */ + if ( gcry_sexp_build (&s_hash, NULL, "%m", frame) ) + BUG (); + gcry_mpi_release (frame); + + rc = gcry_pk_verify (s_sig, s_hash, s_pkey); + if (DBG_CRYPTO) + log_debug ("gcry_pk_verify: %s\n", gpg_strerror (rc)); + gcry_sexp_release (s_sig); + gcry_sexp_release (s_hash); + gcry_sexp_release (s_pkey); + return rc; +} + + + +int +gpgsm_create_cms_signature (KsbaCert cert, gcry_md_hd_t md, int mdalgo, + char **r_sigval) +{ + int rc; + char *grip; + size_t siglen; + + grip = gpgsm_get_keygrip_hexstring (cert); + if (!grip) + return gpg_error (GPG_ERR_BAD_CERT); + + rc = gpgsm_agent_pksign (grip, gcry_md_read(md, mdalgo), + gcry_md_get_algo_dlen (mdalgo), mdalgo, + r_sigval, &siglen); + xfree (grip); + return rc; +} + + + diff --git a/sm/certdump.c b/sm/certdump.c new file mode 100644 index 000000000..703e07186 --- /dev/null +++ b/sm/certdump.c @@ -0,0 +1,457 @@ +/* certdump.c - Dump a certificate for debugging + * Copyright (C) 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + +struct dn_array_s { + char *key; + char *value; +}; + + +/* print the first element of an S-Expression */ +void +gpgsm_print_serial (FILE *fp, KsbaConstSexp p) +{ + unsigned long n; + KsbaConstSexp endp; + + if (!p) + fputs (_("none"), fp); + else if (*p != '(') + fputs ("[Internal error - not an S-expression]", fp); + else + { + p++; + n = strtoul (p, (char**)&endp, 10); + p = endp; + if (*p!=':') + fputs ("[Internal Error - invalid S-expression]", fp); + else + { + for (p++; n; n--, p++) + fprintf (fp, "%02X", *p); + } + } +} + + +void +gpgsm_dump_serial (KsbaConstSexp p) +{ + unsigned long n; + KsbaConstSexp endp; + + if (!p) + log_printf ("none"); + else if (*p != '(') + log_printf ("ERROR - not an S-expression"); + else + { + p++; + n = strtoul (p, (char**)&endp, 10); + p = endp; + if (*p!=':') + log_printf ("ERROR - invalid S-expression"); + else + { + for (p++; n; n--, p++) + log_printf ("%02X", *p); + } + } +} + +void +gpgsm_print_time (FILE *fp, time_t t) +{ + if (!t) + fputs (_("none"), fp); + else if ( t == (time_t)(-1) ) + fputs ("[Error - Invalid time]", fp); + else + { + struct tm *tp; + + tp = gmtime (&t); + fprintf (fp, "%04d-%02d-%02d %02d:%02d:%02d Z", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec); + assert (!tp->tm_isdst); + } +} + +void +gpgsm_dump_time (time_t t) +{ + + if (!t) + log_printf (_("[none]")); + else if ( t == (time_t)(-1) ) + log_printf (_("[error]")); + else + { + struct tm *tp; + + tp = gmtime (&t); + log_printf ("%04d-%02d-%02d %02d:%02d:%02d", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, + tp->tm_hour, tp->tm_min, tp->tm_sec); + assert (!tp->tm_isdst); + } +} + + + + +void +gpgsm_dump_string (const char *string) +{ + + if (!string) + log_printf ("[error]"); + else + { + const unsigned char *s; + + for (s=string; *s; s++) + { + if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0)) + break; + } + if (!*s && *string != '[') + log_printf ("%s", string); + else + { + log_printf ( "[ "); + log_printhex (NULL, string, strlen (string)); + log_printf ( " ]"); + } + } +} + + +void +gpgsm_dump_cert (const char *text, KsbaCert cert) +{ + KsbaSexp sexp; + unsigned char *p; + char *dn; + time_t t; + + log_debug ("BEGIN Certificate `%s':\n", text? text:""); + if (cert) + { + sexp = ksba_cert_get_serial (cert); + log_debug (" serial: "); + gpgsm_dump_serial (sexp); + ksba_free (sexp); + log_printf ("\n"); + + t = ksba_cert_get_validity (cert, 0); + log_debug (" notBefore: "); + gpgsm_dump_time (t); + log_printf ("\n"); + t = ksba_cert_get_validity (cert, 1); + log_debug (" notAfter: "); + gpgsm_dump_time (t); + log_printf ("\n"); + + dn = ksba_cert_get_issuer (cert, 0); + log_debug (" issuer: "); + gpgsm_dump_string (dn); + ksba_free (dn); + log_printf ("\n"); + + dn = ksba_cert_get_subject (cert, 0); + log_debug (" subject: "); + gpgsm_dump_string (dn); + ksba_free (dn); + log_printf ("\n"); + + log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert)); + + p = gpgsm_get_fingerprint_string (cert, 0); + log_debug (" SHA1 Fingerprint: %s\n", p); + xfree (p); + } + log_debug ("END Certificate\n"); +} + + + +/* helper for the rfc2253 string parser */ +static const unsigned char * +parse_dn_part (struct dn_array_s *array, const unsigned char *string) +{ + const unsigned char *s, *s1; + size_t n; + unsigned char *p; + + /* parse attributeType */ + for (s = string+1; *s && *s != '='; s++) + ; + if (!*s) + return NULL; /* error */ + n = s - string; + if (!n) + return NULL; /* empty key */ + array->key = p = xtrymalloc (n+1); + if (!array->key) + return NULL; + memcpy (p, string, n); + p[n] = 0; + trim_trailing_spaces (p); + if ( !strcmp (p, "1.2.840.113549.1.9.1") ) + strcpy (p, "EMail"); + string = s + 1; + + if (*string == '#') + { /* hexstring */ + string++; + for (s=string; hexdigitp (s); s++) + s++; + n = s - string; + if (!n || (n & 1)) + return NULL; /* empty or odd number of digits */ + n /= 2; + array->value = p = xtrymalloc (n+1); + if (!p) + return NULL; + for (s1=string; n; s1 += 2, n--) + *p++ = xtoi_2 (s1); + *p = 0; + } + else + { /* regular v3 quoted string */ + for (n=0, s=string; *s; s++) + { + if (*s == '\\') + { /* pair */ + s++; + if (*s == ',' || *s == '=' || *s == '+' + || *s == '<' || *s == '>' || *s == '#' || *s == ';' + || *s == '\\' || *s == '\"' || *s == ' ') + n++; + else if (hexdigitp (s) && hexdigitp (s+1)) + { + s++; + n++; + } + else + return NULL; /* invalid escape sequence */ + } + else if (*s == '\"') + return NULL; /* invalid encoding */ + else if (*s == ',' || *s == '=' || *s == '+' + || *s == '<' || *s == '>' || *s == '#' || *s == ';' ) + break; + else + n++; + } + + array->value = p = xtrymalloc (n+1); + if (!p) + return NULL; + for (s=string; n; s++, n--) + { + if (*s == '\\') + { + s++; + if (hexdigitp (s)) + { + *p++ = xtoi_2 (s); + s++; + } + else + *p++ = *s; + } + else + *p++ = *s; + } + *p = 0; + } + return s; +} + + +/* Parse a DN and return an array-ized one. This is not a validating + parser and it does not support any old-stylish syntax; KSBA is + expected to return only rfc2253 compatible strings. */ +static struct dn_array_s * +parse_dn (const unsigned char *string) +{ + struct dn_array_s *array; + size_t arrayidx, arraysize; + int i; + + arraysize = 7; /* C,ST,L,O,OU,CN,email */ + arrayidx = 0; + array = xtrymalloc ((arraysize+1) * sizeof *array); + if (!array) + return NULL; + while (*string) + { + while (*string == ' ') + string++; + if (!*string) + break; /* ready */ + if (arrayidx >= arraysize) + { + struct dn_array_s *a2; + + arraysize += 5; + a2 = xtryrealloc (array, (arraysize+1) * sizeof *array); + if (!a2) + goto failure; + array = a2; + } + array[arrayidx].key = NULL; + array[arrayidx].value = NULL; + string = parse_dn_part (array+arrayidx, string); + arrayidx++; + if (!string) + goto failure; + while (*string == ' ') + string++; + if (*string && *string != ',' && *string != ';' && *string != '+') + goto failure; /* invalid delimiter */ + if (*string) + string++; + } + array[arrayidx].key = NULL; + array[arrayidx].value = NULL; + return array; + + failure: + for (i=0; i < arrayidx; i++) + { + xfree (array[i].key); + xfree (array[i].value); + } + xfree (array); + return NULL; +} + + +static void +print_dn_part (FILE *fp, struct dn_array_s *dn, const char *key) +{ + int any = 0; + + for (; dn->key; dn++) + { + if (!strcmp (dn->key, key) && dn->value && *dn->value) + { + putc ('/', fp); + if (any) + fputs (" + ", fp); + else + fprintf (fp, "%s=", key); + print_sanitized_utf8_string (fp, dn->value, '/'); + any = 1; + } + } +} + +/* Print all parts of a DN in a "standard" sequence. We first print + all the known parts, followed by the uncommon ones */ +static void +print_dn_parts (FILE *fp, struct dn_array_s *dn) +{ + const char *stdpart[] = { + "CN", "OU", "O", "STREET", "L", "ST", "C", "EMail", NULL + }; + int i; + + for (i=0; stdpart[i]; i++) + print_dn_part (fp, dn, stdpart[i]); + + /* now print the rest without any specific ordering */ + for (; dn->key; dn++) + { + for (i=0; stdpart[i]; i++) + { + if (!strcmp (dn->key, stdpart[i])) + break; + } + if (!stdpart[i]) + print_dn_part (fp, dn, dn->key); + } +} + + + +void +gpgsm_print_name (FILE *fp, const char *name) +{ + const unsigned char *s; + int i; + + s = name; + if (!s) + { + fputs (_("[Error - No name]"), fp); + } + else if (*s == '<') + { + const unsigned char *s2 = strchr (s+1, '>'); + if (s2) + print_sanitized_utf8_buffer (fp, s + 1, s2 - s - 1, 0); + } + else if (*s == '(') + fputs (_("[Error - unknown encoding]"), fp); + else if (!((*s >= '0' && *s < '9') + || (*s >= 'A' && *s <= 'Z') + || (*s >= 'a' && *s <= 'z'))) + fputs (_("[Error - invalid encoding]"), fp); + else + { + struct dn_array_s *dn = parse_dn (s); + if (!dn) + fputs (_("[Error - invalid DN]"), fp); + else + { + print_dn_parts (fp, dn); + for (i=0; dn[i].key; i++) + { + xfree (dn[i].key); + xfree (dn[i].value); + } + xfree (dn); + } + } +} + + + diff --git a/sm/certlist.c b/sm/certlist.c new file mode 100644 index 000000000..eedc99025 --- /dev/null +++ b/sm/certlist.c @@ -0,0 +1,315 @@ +/* certlist.c - build list of certificates + * Copyright (C) 2001, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + +/* Return 0 if the cert is usable for encryption. A MODE of 0 checks + for signing a MODE of 1 checks for encryption, a MODE of 2 checks + for verification and a MODE of 3 for decryption (just for + debugging) */ +static int +cert_usage_p (KsbaCert cert, int mode) +{ + KsbaError err; + unsigned int use; + + err = ksba_cert_get_key_usage (cert, &use); + if (err == KSBA_No_Data) + { + if (opt.verbose && mode < 2) + log_info (mode? + _("no key usage specified - accepted for encryption\n"): + _("no key usage specified - accepted for signing\n")); + return 0; + } + if (err) + { + log_error (_("error getting key usage information: %s\n"), + ksba_strerror (err)); + return map_ksba_err (err); + } + + if (mode == 4) + { + if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN))) + return 0; + log_info ( _("certificate should have not been used certification\n")); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); + } + + if ((use & ((mode&1)? + (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT): + (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) + ) + return 0; + log_info (mode==3? _("certificate should have not been used for encryption\n"): + mode==2? _("certificate should have not been used for signing\n"): + mode==1? _("certificate is not usable for encryption\n"): + _("certificate is not usable for signing\n")); + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); +} + + +/* Return 0 if the cert is usable for signing */ +int +gpgsm_cert_use_sign_p (KsbaCert cert) +{ + return cert_usage_p (cert, 0); +} + + +/* Return 0 if the cert is usable for encryption */ +int +gpgsm_cert_use_encrypt_p (KsbaCert cert) +{ + return cert_usage_p (cert, 1); +} + +int +gpgsm_cert_use_verify_p (KsbaCert cert) +{ + return cert_usage_p (cert, 2); +} + +int +gpgsm_cert_use_decrypt_p (KsbaCert cert) +{ + return cert_usage_p (cert, 3); +} + +int +gpgsm_cert_use_cert_p (KsbaCert cert) +{ + return cert_usage_p (cert, 4); +} + + +static int +same_subject_issuer (const char *subject, const char *issuer, KsbaCert cert) +{ + char *subject2 = ksba_cert_get_subject (cert, 0); + char *issuer2 = ksba_cert_get_subject (cert, 0); + int tmp; + + tmp = (subject && subject2 + && !strcmp (subject, subject2) + && issuer && issuer2 + && !strcmp (issuer, issuer2)); + xfree (subject2); + xfree (issuer2); + return tmp; +} + + + +/* Add a certificate to a list of certificate and make sure that it is + a valid certificate. With SECRET set to true a secret key must be + avaibale for the certificate. */ +int +gpgsm_add_to_certlist (CTRL ctrl, const char *name, int secret, + CERTLIST *listaddr) +{ + int rc; + KEYDB_SEARCH_DESC desc; + KEYDB_HANDLE kh = NULL; + KsbaCert cert = NULL; + + rc = keydb_classify_name (name, &desc); + if (!rc) + { + kh = keydb_new (0); + if (!kh) + rc = gpg_error (GPG_ERR_ENOMEM); + else + { + int wrong_usage = 0; + char *subject = NULL; + char *issuer = NULL; + + get_next: + rc = keydb_search (kh, &desc, 1); + if (!rc) + rc = keydb_get_cert (kh, &cert); + if (!rc) + { + rc = secret? gpgsm_cert_use_sign_p (cert) + : gpgsm_cert_use_encrypt_p (cert); + if (gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE) + { + /* There might be another certificate with the + correct usage, so we try again */ + if (!wrong_usage) + { /* save the first match */ + wrong_usage = rc; + subject = ksba_cert_get_subject (cert, 0); + issuer = ksba_cert_get_subject (cert, 0); + ksba_cert_release (cert); + cert = NULL; + goto get_next; + } + else if (same_subject_issuer (subject, issuer, cert)) + { + wrong_usage = rc; + ksba_cert_release (cert); + cert = NULL; + goto get_next; + } + else + wrong_usage = rc; + + } + } + /* we want the error code from the first match in this case */ + if (rc && wrong_usage) + rc = wrong_usage; + + if (!rc) + { + next_ambigious: + rc = keydb_search (kh, &desc, 1); + if (rc == -1) + rc = 0; + else if (!rc) + { + KsbaCert cert2 = NULL; + + /* We have to ignore ambigious names as long as + there only fault is a bad key usage */ + if (!keydb_get_cert (kh, &cert2)) + { + int tmp = (same_subject_issuer (subject, issuer, cert2) + && ((gpg_err_code ( + secret? gpgsm_cert_use_sign_p (cert2) + : gpgsm_cert_use_encrypt_p (cert2) + ) + ) == GPG_ERR_WRONG_KEY_USAGE)); + ksba_cert_release (cert2); + if (tmp) + goto next_ambigious; + } + rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + } + } + xfree (subject); + xfree (issuer); + + if (!rc && secret) + { + char *p; + + rc = gpg_error (GPG_ERR_NO_SECKEY); + p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + if (!gpgsm_agent_havekey (p)) + rc = 0; + xfree (p); + } + } + if (!rc) + rc = gpgsm_validate_chain (ctrl, cert, NULL); + if (!rc) + { + CERTLIST cl = xtrycalloc (1, sizeof *cl); + if (!cl) + rc = OUT_OF_CORE (errno); + else + { + cl->cert = cert; cert = NULL; + cl->next = *listaddr; + *listaddr = cl; + } + } + } + } + + keydb_release (kh); + ksba_cert_release (cert); + return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc; +} + +void +gpgsm_release_certlist (CERTLIST list) +{ + while (list) + { + CERTLIST cl = list->next; + ksba_cert_release (list->cert); + xfree (list); + list = cl; + } +} + + +/* Like gpgsm_add_to_certlist, but look only for one certificate. No + chain validation is done */ +int +gpgsm_find_cert (const char *name, KsbaCert *r_cert) +{ + int rc; + KEYDB_SEARCH_DESC desc; + KEYDB_HANDLE kh = NULL; + + *r_cert = NULL; + rc = keydb_classify_name (name, &desc); + if (!rc) + { + kh = keydb_new (0); + if (!kh) + rc = gpg_error (GPG_ERR_ENOMEM); + else + { + rc = keydb_search (kh, &desc, 1); + if (!rc) + rc = keydb_get_cert (kh, r_cert); + if (!rc) + { + rc = keydb_search (kh, &desc, 1); + if (rc == -1) + rc = 0; + else + { + if (!rc) + rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + ksba_cert_release (*r_cert); + *r_cert = NULL; + } + } + } + } + + keydb_release (kh); + return rc == -1? gpg_error (GPG_ERR_NO_PUBKEY): rc; +} + diff --git a/sm/certreqgen.c b/sm/certreqgen.c new file mode 100644 index 000000000..0dd4fdde9 --- /dev/null +++ b/sm/certreqgen.c @@ -0,0 +1,699 @@ +/* certreqgen.c - Generate a key and a certification request + * Copyright (C) 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* +The format of the native parameter file is follows: + o Text only, line length is limited to about 1000 chars. + o You must use UTF-8 encoding to specify non-ascii characters. + o Empty lines are ignored. + o Leading and trailing spaces are ignored. + o A hash sign as the first non white space character is a comment line. + o Control statements are indicated by a leading percent sign, the + arguments are separated by white space from the keyword. + o Parameters are specified by a keyword, followed by a colon. Arguments + are separated by white space. + o The first parameter must be "Key-Type", control statements + may be placed anywhere. + o Key generation takes place when either the end of the parameter file + is reached, the next "Key-Type" parameter is encountered or at the + controlstatement "%commit" + o Control statements: + %echo <text> + Print <text>. + %dry-run + Suppress actual key generation (useful for syntax checking). + %commit + Perform the key generation. Note that an implicit commit is done + at the next "Key-Type" parameter. + %certfile <filename> + Do not write the certificate to the keyDB but to <filename>. + This must be given before the first + commit to take place, duplicate specification of the same filename + is ignored, the last filename before a commit is used. + The filename is used until a new filename is used (at commit points) + and all keys are written to that file. If a new filename is given, + this file is created (and overwrites an existing one). + Both control statements must be given. + o The order of the parameters does not matter except for "Key-Type" + which must be the first parameter. The parameters are only for the + generated keyblock and parameters from previous key generations are not + used. Some syntactically checks may be performed. + The currently defined parameters are: + Key-Type: <algo> + Starts a new parameter block by giving the type of the + primary key. The algorithm must be capable of signing. + This is a required parameter. For now the only supported + algorithm is "rsa". + Key-Length: <length-in-bits> + Length of the key in bits. Default is 1024. + Key-Usage: <usage-list> + Space or comma delimited list of key usage, allowed values are + "encrypt" and "sign". This is used to generate the KeyUsage extension. + Please make sure that the algorithm is capable of this usage. Default + is to allow encrypt and sign. + Name-DN: subject name + This is the DN name of the subject in rfc2253 format. + Name-Email: <string> + The ist the email address + +Here is an example: +$ cat >foo <<EOF +%echo Generating a standard key +Key-Type: RSA +Key-Length: 1024 +Name-DN: CN=test cert 1,OU=Aegypten Project,O=g10 Code GmbH,L=Düsseldorf,C=DE +Name-Email: [email protected] +# Do a commit here, so that we can later print "done" :-) +%commit +%echo done +EOF +*/ + + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +enum para_name { + pKEYTYPE, + pKEYLENGTH, + pKEYUSAGE, + pNAMEDN, + pNAMEEMAIL +}; + +struct para_data_s { + struct para_data_s *next; + int lnr; + enum para_name key; + union { + unsigned int usage; + char value[1]; + } u; +}; + +struct reqgen_ctrl_s { + int lnr; + int dryrun; + KsbaWriter writer; +}; + + +static int proc_parameters (struct para_data_s *para, + struct reqgen_ctrl_s *outctrl); +static int create_request (struct para_data_s *para, + KsbaConstSexp public, + struct reqgen_ctrl_s *outctrl); + + + +static void +release_parameter_list (struct para_data_s *r) +{ + struct para_data_s *r2; + + for (; r ; r = r2) + { + r2 = r->next; + xfree(r); + } +} + +static struct para_data_s * +get_parameter (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r; + + for (r = para; r && r->key != key; r = r->next) + ; + return r; +} + +static const char * +get_parameter_value (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key); + return (r && *r->u.value)? r->u.value : NULL; +} + +static int +get_parameter_algo (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key); + if (!r) + return -1; + if (digitp (r->u.value)) + return atoi( r->u.value ); + return gcry_pk_map_name (r->u.value); +} + +/* parse the usage parameter. Returns 0 on success. Note that we + only care about sign and encrypt and don't (yet) allow all the + other X.509 usage to be specified; instead we will use a fixed + mapping to the X.509 usage flags */ +static int +parse_parameter_usage (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key); + char *p, *pn; + unsigned int use; + + if (!r) + return 0; /* none (this is an optional parameter)*/ + + use = 0; + pn = r->u.value; + while ( (p = strsep (&pn, " \t,")) ) + { + if (!*p) + ; + else if ( !ascii_strcasecmp (p, "sign") ) + use |= GCRY_PK_USAGE_SIGN; + else if ( !ascii_strcasecmp (p, "encrypt") ) + use |= GCRY_PK_USAGE_ENCR; + else + { + log_error ("line %d: invalid usage list\n", r->lnr); + return -1; /* error */ + } + } + r->u.usage = use; + return 0; +} + + +static unsigned int +get_parameter_uint (struct para_data_s *para, enum para_name key) +{ + struct para_data_s *r = get_parameter (para, key); + + if (!r) + return 0; + + return (unsigned int)strtoul (r->u.value, NULL, 10); +} + + + +/* Read the certificate generation parameters from FP and generate + (all) certificate requests. */ +static int +read_parameters (FILE *fp, KsbaWriter writer) +{ + static struct { + const char *name; + enum para_name key; + } keywords[] = { + { "Key-Type", pKEYTYPE}, + { "Key-Length", pKEYLENGTH }, + { "Key-Usage", pKEYUSAGE }, + { "Name-DN", pNAMEDN }, + { "Name-Email", pNAMEEMAIL }, + { NULL, 0 } + }; + char line[1024], *p; + const char *err = NULL; + struct para_data_s *para, *r; + int i, rc = 0, any = 0; + struct reqgen_ctrl_s outctrl; + + memset (&outctrl, 0, sizeof (outctrl)); + outctrl.writer = writer; + + err = NULL; + para = NULL; + while (fgets (line, DIM(line)-1, fp) ) + { + char *keyword, *value; + + outctrl.lnr++; + if (*line && line[strlen(line)-1] != '\n') + { + err = "line too long"; + break; + } + for (p=line; spacep (p); p++) + ; + if (!*p || *p == '#') + continue; + + keyword = p; + if (*keyword == '%') + { + for (; !spacep (p); p++) + ; + if (*p) + *p++ = 0; + for (; spacep (p); p++) + ; + value = p; + trim_trailing_spaces (value); + + if (!ascii_strcasecmp (keyword, "%echo")) + log_info ("%s\n", value); + else if (!ascii_strcasecmp (keyword, "%dry-run")) + outctrl.dryrun = 1; + else if (!ascii_strcasecmp( keyword, "%commit")) + { + rc = proc_parameters (para, &outctrl); + if (rc) + goto leave; + any = 1; + release_parameter_list (para); + para = NULL; + } + else + log_info ("skipping control `%s' (%s)\n", keyword, value); + + continue; + } + + + if (!(p = strchr (p, ':')) || p == keyword) + { + err = "missing colon"; + break; + } + if (*p) + *p++ = 0; + for (; spacep (p); p++) + ; + if (!*p) + { + err = "missing argument"; + break; + } + value = p; + trim_trailing_spaces (value); + + for (i=0; (keywords[i].name + && ascii_strcasecmp (keywords[i].name, keyword)); i++) + ; + if (!keywords[i].name) + { + err = "unknown keyword"; + break; + } + if (keywords[i].key != pKEYTYPE && !para) + { + err = "parameter block does not start with \"Key-Type\""; + break; + } + + if (keywords[i].key == pKEYTYPE && para) + { + rc = proc_parameters (para, &outctrl); + if (rc) + goto leave; + any = 1; + release_parameter_list (para); + para = NULL; + } + else + { + for (r = para; r && r->key != keywords[i].key; r = r->next) + ; + if (r) + { + err = "duplicate keyword"; + break; + } + } + + r = xtrycalloc (1, sizeof *r + strlen( value )); + if (!r) + { + err = "out of core"; + break; + } + r->lnr = outctrl.lnr; + r->key = keywords[i].key; + strcpy (r->u.value, value); + r->next = para; + para = r; + } + + if (err) + { + log_error ("line %d: %s\n", outctrl.lnr, err); + rc = gpg_error (GPG_ERR_GENERAL); + } + else if (ferror(fp)) + { + log_error ("line %d: read error: %s\n", outctrl.lnr, strerror(errno) ); + rc = gpg_error (GPG_ERR_GENERAL); + } + else if (para) + { + rc = proc_parameters (para, &outctrl); + if (rc) + goto leave; + any = 1; + } + + if (!rc && !any) + rc = gpg_error (GPG_ERR_NO_DATA); + + leave: + release_parameter_list (para); + return rc; +} + +/* check whether there are invalid characters in the email address S */ +static int +has_invalid_email_chars (const char *s) +{ + int at_seen=0; + static char valid_chars[] = "01234567890_-." + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + for (; *s; s++) + { + if (*s & 0x80) + return 1; + if (*s == '@') + at_seen++; + else if (!at_seen && !( !!strchr (valid_chars, *s) || *s == '+')) + return 1; + else if (at_seen && !strchr (valid_chars, *s)) + return 1; + } + return at_seen != 1; +} + + +/* Check that all required parameters are given and perform the action */ +static int +proc_parameters (struct para_data_s *para, struct reqgen_ctrl_s *outctrl) +{ + struct para_data_s *r; + const char *s; + int i; + unsigned int nbits; + char numbuf[20]; + unsigned char keyparms[100]; + int rc; + KsbaSexp public; + + /* check that we have all required parameters */ + assert (get_parameter (para, pKEYTYPE)); + + /* We can only use RSA for now. There is a with pkcs-10 on how to + use ElGamal becuase it is expected that a PK algorithm can always + be used for signing. */ + i = get_parameter_algo (para, pKEYTYPE); + if (i < 1 || i != GCRY_PK_RSA ) + { + r = get_parameter (para, pKEYTYPE); + log_error ("line %d: invalid algorithm\n", r->lnr); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* check the keylength */ + if (!get_parameter (para, pKEYLENGTH)) + nbits = 1024; + else + nbits = get_parameter_uint (para, pKEYLENGTH); + if (nbits < 512 || nbits > 4096) + { + r = get_parameter (para, pKEYTYPE); + log_error ("line %d: invalid key length %u (valid are 512 to 4096)\n", + r->lnr, nbits); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + + /* check the usage */ + if (parse_parameter_usage (para, pKEYUSAGE)) + return gpg_error (GPG_ERR_INV_PARAMETER); + + /* check that there is a subject name and that this DN fits our + requirements */ + if (!(s=get_parameter_value (para, pNAMEDN))) + { + r = get_parameter (para, pKEYTYPE); + log_error ("line %d: no subject name given\n", r->lnr); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + /* fixme check s */ + + /* check that the optional email address is okay */ + if ((s=get_parameter_value (para, pNAMEEMAIL))) + { + if (has_invalid_email_chars (s) + || *s == '@' + || s[strlen(s)-1] == '@' + || s[strlen(s)-1] == '.' + || strstr(s, "..")) + { + r = get_parameter (para, pKEYTYPE); + log_error ("line %d: not a valid email address\n", r->lnr); + return gpg_error (GPG_ERR_INV_PARAMETER); + } + } + + sprintf (numbuf, "%u", nbits); + snprintf (keyparms, DIM (keyparms)-1, + "(6:genkey(3:rsa(5:nbits%d:%s)))", strlen (numbuf), numbuf); + rc = gpgsm_agent_genkey (keyparms, &public); + if (rc) + { + r = get_parameter (para, pKEYTYPE); + log_error ("line %d: key generation failed: %s\n", + r->lnr, gpg_strerror (rc)); + return rc; + } + + rc = create_request (para, public, outctrl); + xfree (public); + + return rc; +} + + +/* Parameters are checked, the key pair has been created. Now + generate the request and write it out */ +static int +create_request (struct para_data_s *para, KsbaConstSexp public, + struct reqgen_ctrl_s *outctrl) +{ + KsbaCertreq cr; + KsbaError err; + gcry_md_hd_t md; + KsbaStopReason stopreason; + int rc = 0; + const char *s; + + cr = ksba_certreq_new (); + if (!cr) + return gpg_error (GPG_ERR_ENOMEM); + + rc = gcry_md_open (&md, GCRY_MD_SHA1, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "cr.cri"); + + ksba_certreq_set_hash_function (cr, HASH_FNC, md); + ksba_certreq_set_writer (cr, outctrl->writer); + + err = ksba_certreq_add_subject (cr, get_parameter_value (para, pNAMEDN)); + if (err) + { + log_error ("error setting the subject's name: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + s = get_parameter_value (para, pNAMEEMAIL); + if (s) + { + char *buf; + + buf = xtrymalloc (strlen (s) + 3); + if (!buf) + { + rc = OUT_OF_CORE (errno); + goto leave; + } + *buf = '<'; + strcpy (buf+1, s); + strcat (buf+1, ">"); + err = ksba_certreq_add_subject (cr, buf); + xfree (buf); + if (err) + { + log_error ("error setting the subject's alternate name: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + + + err = ksba_certreq_set_public_key (cr, public); + if (err) + { + log_error ("error setting the public key: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + do + { + err = ksba_certreq_build (cr, &stopreason); + if (err) + { + log_error ("ksba_certreq_build failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + if (stopreason == KSBA_SR_NEED_SIG) + { + gcry_sexp_t s_pkey; + size_t n; + unsigned char grip[20], hexgrip[41]; + char *sigval; + size_t siglen; + + n = gcry_sexp_canon_len (public, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + rc = gcry_sexp_sscan (&s_pkey, NULL, public, n); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if ( !gcry_pk_get_keygrip (s_pkey, grip) ) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error ("can't figure out the keygrip\n"); + gcry_sexp_release (s_pkey); + goto leave; + } + gcry_sexp_release (s_pkey); + for (n=0; n < 20; n++) + sprintf (hexgrip+n*2, "%02X", grip[n]); + + rc = gpgsm_agent_pksign (hexgrip, + gcry_md_read(md, GCRY_MD_SHA1), + gcry_md_get_algo_dlen (GCRY_MD_SHA1), + GCRY_MD_SHA1, + &sigval, &siglen); + if (rc) + { + log_error ("signing failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + err = ksba_certreq_set_sig_val (cr, sigval); + xfree (sigval); + if (err) + { + log_error ("failed to store the sig_val: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + } + while (stopreason != KSBA_SR_READY); + + + leave: + gcry_md_close (md); + ksba_certreq_release (cr); + return rc; +} + + + +/* Create a new key by reading the parameters from in_fd. Multiple + keys may be created */ +int +gpgsm_genkey (CTRL ctrl, int in_fd, FILE *out_fp) +{ + int rc; + FILE *in_fp; + Base64Context b64writer = NULL; + KsbaWriter writer; + + in_fp = fdopen (dup (in_fd), "rb"); + if (!in_fp) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + return tmperr; + } + + ctrl->pem_name = "NEW CERTIFICATE REQUEST"; + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = read_parameters (in_fp, writer); + if (rc) + { + log_error ("error creating certificate request: %s\n", + gpg_strerror (rc)); + goto leave; + } + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + gpgsm_status (ctrl, STATUS_KEY_CREATED, "P"); + log_info ("certificate request created\n"); + + leave: + gpgsm_destroy_writer (b64writer); + fclose (in_fp); + return rc; +} + diff --git a/sm/decrypt.c b/sm/decrypt.c new file mode 100644 index 000000000..17483aa49 --- /dev/null +++ b/sm/decrypt.c @@ -0,0 +1,506 @@ +/* decrypt.c - Decrypt a message + * Copyright (C) 2001, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + +struct decrypt_filter_parm_s { + int algo; + int mode; + int blklen; + gcry_cipher_hd_t hd; + char iv[16]; + size_t ivlen; + int any_data; /* dod we push anything through the filter at all? */ + unsigned char lastblock[16]; /* to strip the padding we have to + keep this one */ + char helpblock[16]; /* needed because there is no block buffering in + libgcrypt (yet) */ + int helpblocklen; +}; + + + +/* decrypt the session key and fill in the parm structure. The + algo and the IV is expected to be already in PARM. */ +static int +prepare_decryption (const char *hexkeygrip, KsbaConstSexp enc_val, + struct decrypt_filter_parm_s *parm) +{ + char *seskey = NULL; + size_t n, seskeylen; + int rc; + + rc = gpgsm_agent_pkdecrypt (hexkeygrip, enc_val, + &seskey, &seskeylen); + if (rc) + { + log_error ("error decrypting session key: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (DBG_CRYPTO) + log_printhex ("pkcs1 encoded session key:", seskey, seskeylen); + + n=0; + if (seskeylen == 24) + { + /* Smells like a 3-des key. This might happen because a SC has + already done the unpacking. fixme! */ + } + else + { + if (n + 7 > seskeylen ) + { + rc = gpg_error (GPG_ERR_INV_SESSION_KEY); + goto leave; + } + + /* FIXME: Actually the leading zero is required but due to the way + we encode the output in libgcrypt as an MPI we are not able to + encode that leading zero. However, when using a Smartcard we are + doing it the rightway and therefore we have to skip the zero. This + should be fixed in gpg-agent of course. */ + if (!seskey[n]) + n++; + + if (seskey[n] != 2 ) /* wrong block type version */ + { + rc = gpg_error (GPG_ERR_INV_SESSION_KEY); + goto leave; + } + + for (n++; n < seskeylen && seskey[n]; n++) /* skip the random bytes */ + ; + n++; /* and the zero byte */ + if (n >= seskeylen ) + { + rc = gpg_error (GPG_ERR_INV_SESSION_KEY); + goto leave; + } + } + + if (DBG_CRYPTO) + log_printhex ("session key:", seskey+n, seskeylen-n); + + rc = gcry_cipher_open (&parm->hd, parm->algo, parm->mode, 0); + if (rc) + { + log_error ("error creating decryptor: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = gcry_cipher_setkey (parm->hd, seskey+n, seskeylen-n); + if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY) + { + log_info (_("WARNING: message was encrypted with " + "a weak key in the symmetric cipher.\n")); + rc = 0; + } + if (rc) + { + log_error("key setup failed: %s\n", gpg_strerror(rc) ); + goto leave; + } + + gcry_cipher_setiv (parm->hd, parm->iv, parm->ivlen); + + leave: + xfree (seskey); + return rc; +} + + +/* This function is called by the KSBA writer just before the actual + write is done. The function must take INLEN bytes from INBUF, + decrypt it and store it inoutbuf which has a maximum size of + maxoutlen. The valid bytes in outbuf should be return in outlen. + Due to different buffer sizes or different length of input and + output, it may happen that fewer bytes are process or fewer bytes + are written. */ +static KsbaError +decrypt_filter (void *arg, + const void *inbuf, size_t inlen, size_t *inused, + void *outbuf, size_t maxoutlen, size_t *outlen) +{ + struct decrypt_filter_parm_s *parm = arg; + int blklen = parm->blklen; + size_t orig_inlen = inlen; + + /* fixme: Should we issue an error when we have not seen one full block? */ + if (!inlen) + return KSBA_Bug; + + if (maxoutlen < 2*parm->blklen) + return KSBA_Bug; + /* make some space becuase we will later need an extra block at the end */ + maxoutlen -= blklen; + + if (parm->helpblocklen) + { + int i, j; + + for (i=parm->helpblocklen,j=0; i < blklen && j < inlen; i++, j++) + parm->helpblock[i] = ((const char*)inbuf)[j]; + inlen -= j; + if (blklen > maxoutlen) + return KSBA_Bug; + if (i < blklen) + { + parm->helpblocklen = i; + *outlen = 0; + } + else + { + parm->helpblocklen = 0; + if (parm->any_data) + { + memcpy (outbuf, parm->lastblock, blklen); + *outlen =blklen; + } + else + *outlen = 0; + gcry_cipher_decrypt (parm->hd, parm->lastblock, blklen, + parm->helpblock, blklen); + parm->any_data = 1; + } + *inused = orig_inlen - inlen; + return 0; + } + + + if (inlen > maxoutlen) + inlen = maxoutlen; + if (inlen % blklen) + { /* store the remainder away */ + parm->helpblocklen = inlen%blklen; + inlen = inlen/blklen*blklen; + memcpy (parm->helpblock, (const char*)inbuf+inlen, parm->helpblocklen); + } + + *inused = inlen + parm->helpblocklen; + if (inlen) + { + assert (inlen >= blklen); + if (parm->any_data) + { + gcry_cipher_decrypt (parm->hd, (char*)outbuf+blklen, inlen, + inbuf, inlen); + memcpy (outbuf, parm->lastblock, blklen); + memcpy (parm->lastblock,(char*)outbuf+inlen, blklen); + *outlen = inlen; + } + else + { + gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen); + memcpy (parm->lastblock, (char*)outbuf+inlen-blklen, blklen); + *outlen = inlen - blklen; + parm->any_data = 1; + } + } + else + *outlen = 0; + return 0; +} + + + +/* Perform a decrypt operation. */ +int +gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp) +{ + int rc; + KsbaError err; + Base64Context b64reader = NULL; + Base64Context b64writer = NULL; + KsbaReader reader; + KsbaWriter writer; + KsbaCMS cms = NULL; + KsbaStopReason stopreason; + KEYDB_HANDLE kh; + int recp; + FILE *in_fp = NULL; + struct decrypt_filter_parm_s dfparm; + + memset (&dfparm, 0, sizeof dfparm); + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + + in_fp = fdopen ( dup (in_fd), "rb"); + if (!in_fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gpgsm_create_reader (&b64reader, ctrl, in_fp, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + cms = ksba_cms_new (); + if (!cms) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + err = ksba_cms_set_reader_writer (cms, reader, writer); + if (err) + { + log_debug ("ksba_cms_set_reader_writer failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + /* parser loop */ + do + { + err = ksba_cms_parse (cms, &stopreason); + if (err) + { + log_debug ("ksba_cms_parse failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + if (stopreason == KSBA_SR_BEGIN_DATA + || stopreason == KSBA_SR_DETACHED_DATA) + { + int algo, mode; + const char *algoid; + int any_key = 0; + + algoid = ksba_cms_get_content_oid (cms, 2/* encryption algo*/); + algo = gcry_cipher_map_name (algoid); + mode = gcry_cipher_mode_from_oid (algoid); + if (!algo || !mode) + { + rc = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + log_error ("unsupported algorithm `%s'\n", algoid? algoid:"?"); + if (algoid && !strcmp (algoid, "1.2.840.113549.3.2")) + log_info (_("(this is the RC2 algorithm)\n")); + else if (!algoid) + log_info (_("(this does not seem to be an encrypted" + " message)\n")); + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.algorithm", + numbuf, algoid?algoid:"?", NULL); + } + + goto leave; + } + dfparm.algo = algo; + dfparm.mode = mode; + dfparm.blklen = gcry_cipher_get_algo_blklen (algo); + if (dfparm.blklen > sizeof (dfparm.helpblock)) + return gpg_error (GPG_ERR_BUG); + + rc = ksba_cms_get_content_enc_iv (cms, + dfparm.iv, + sizeof (dfparm.iv), + &dfparm.ivlen); + if (rc) + { + log_error ("error getting IV: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + for (recp=0; !any_key; recp++) + { + char *issuer; + KsbaSexp serial; + KsbaSexp enc_val; + char *hexkeygrip = NULL; + + err = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial); + if (err == -1 && recp) + break; /* no more recipients */ + if (err) + log_error ("recp %d - error getting info: %s\n", + recp, ksba_strerror (err)); + else + { + KsbaCert cert = NULL; + + log_debug ("recp %d - issuer: `%s'\n", + recp, issuer? issuer:"[NONE]"); + log_debug ("recp %d - serial: ", recp); + gpgsm_dump_serial (serial); + log_printf ("\n"); + + keydb_search_reset (kh); + rc = keydb_search_issuer_sn (kh, issuer, serial); + if (rc) + { + log_error ("failed to find the certificate: %s\n", + gpg_strerror(rc)); + goto oops; + } + + rc = keydb_get_cert (kh, &cert); + if (rc) + { + log_error ("failed to get cert: %s\n", gpg_strerror (rc)); + goto oops; + } + /* Just in case there is a problem with the own + certificate we print this message - should never + happen of course */ + rc = gpgsm_cert_use_decrypt_p (cert); + if (rc) + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.keyusage", + numbuf, NULL); + rc = 0; + } + + hexkeygrip = gpgsm_get_keygrip_hexstring (cert); + + oops: + xfree (issuer); + xfree (serial); + ksba_cert_release (cert); + } + + if (!hexkeygrip) + ; + else if (!(enc_val = ksba_cms_get_enc_val (cms, recp))) + log_error ("recp %d - error getting encrypted session key\n", + recp); + else + { + rc = prepare_decryption (hexkeygrip, enc_val, &dfparm); + xfree (enc_val); + if (rc) + { + log_debug ("decrypting session key failed: %s\n", + gpg_strerror (rc)); + } + else + { /* setup the bulk decrypter */ + any_key = 1; + ksba_writer_set_filter (writer, + decrypt_filter, + &dfparm); + } + } + } + if (!any_key) + { + rc = gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + } + else if (stopreason == KSBA_SR_END_DATA) + { + ksba_writer_set_filter (writer, NULL, NULL); + if (dfparm.any_data) + { /* write the last block with padding removed */ + int i, npadding = dfparm.lastblock[dfparm.blklen-1]; + if (!npadding || npadding > dfparm.blklen) + { + log_error ("invalid padding with value %d\n", npadding); + rc = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + rc = ksba_writer_write (writer, + dfparm.lastblock, + dfparm.blklen - npadding); + if (rc) + { + rc = map_ksba_err (rc); + goto leave; + } + for (i=dfparm.blklen - npadding; i < dfparm.blklen; i++) + { + if (dfparm.lastblock[i] != npadding) + { + log_error ("inconsistent padding\n"); + rc = gpg_error (GPG_ERR_INV_DATA); + goto leave; + } + } + } + } + + } + while (stopreason != KSBA_SR_READY); + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + gpgsm_status (ctrl, STATUS_DECRYPTION_OKAY, NULL); + + + leave: + if (rc) + gpgsm_status (ctrl, STATUS_DECRYPTION_FAILED, NULL); + ksba_cms_release (cms); + gpgsm_destroy_reader (b64reader); + gpgsm_destroy_writer (b64writer); + keydb_release (kh); + if (in_fp) + fclose (in_fp); + if (dfparm.hd) + gcry_cipher_close (dfparm.hd); + return rc; +} + + diff --git a/sm/delete.c b/sm/delete.c new file mode 100644 index 000000000..53eff864c --- /dev/null +++ b/sm/delete.c @@ -0,0 +1,165 @@ +/* delete.c + * Copyright (C) 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +/* Delete a certificate or an secret key from a key database. */ +static int +delete_one (CTRL ctrl, const char *username) +{ + int rc = 0; + KEYDB_SEARCH_DESC desc; + KEYDB_HANDLE kh = NULL; + KsbaCert cert = NULL; + int duplicates = 0; + + rc = keydb_classify_name (username, &desc); + if (rc) + { + log_error (_("certificate `%s' not found: %s\n"), + username, gpg_strerror (rc)); + gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "1", NULL); + goto leave; + } + + kh = keydb_new (0); + if (!kh) + { + log_error ("keydb_new failed\n"); + goto leave; + } + + + rc = keydb_search (kh, &desc, 1); + if (!rc) + rc = keydb_get_cert (kh, &cert); + if (!rc) + { + char fpr[20]; + + gpgsm_get_fingerprint (cert, 0, fpr, NULL); + + next_ambigious: + rc = keydb_search (kh, &desc, 1); + if (rc == -1) + rc = 0; + else if (!rc) + { + KsbaCert cert2 = NULL; + char fpr2[20]; + + /* We ignore all duplicated certificates which might have + been inserted due to program bugs. */ + if (!keydb_get_cert (kh, &cert2)) + { + gpgsm_get_fingerprint (cert2, 0, fpr2, NULL); + ksba_cert_release (cert2); + if (!memcmp (fpr, fpr2, 20)) + { + duplicates++; + goto next_ambigious; + } + } + rc = gpg_error (GPG_ERR_AMBIGUOUS_NAME); + } + } + if (rc) + { + if (rc == -1) + rc = gpg_error (GPG_ERR_NO_PUBKEY); + log_error (_("certificate `%s' not found: %s\n"), + username, gpg_strerror (rc)); + gpgsm_status2 (ctrl, STATUS_DELETE_PROBLEM, "3", NULL); + goto leave; + } + + /* we need to search again to get back to the right position. */ + do + { + keydb_search_reset (kh); + rc = keydb_search (kh, &desc, 1); + if (rc) + { + log_error ("problem re-searching certificate: %s\n", + gpg_strerror (rc)); + goto leave; + } + + rc = keydb_delete (kh); + if (rc) + goto leave; + if (opt.verbose) + { + if (duplicates) + log_info (_("duplicated certificate `%s' deleted\n"), username); + else + log_info (_("certificate `%s' deleted\n"), username); + } + } + while (duplicates--); + + leave: + keydb_release (kh); + ksba_cert_release (cert); + return rc; +} + + + +/* Delete the certificates specified by NAMES. */ +int +gpgsm_delete (CTRL ctrl, STRLIST names) +{ + int rc; + + if (!names) + { + log_error ("nothing to delete\n"); + return gpg_error (GPG_ERR_NO_DATA); + } + + for (; names; names=names->next ) + { + rc = delete_one (ctrl, names->d); + if (rc) + { + log_error (_("deleting certificate \"%s\" failed: %s\n"), + names->d, gpg_strerror (rc) ); + return rc; + } + } + + return 0; +} diff --git a/sm/encrypt.c b/sm/encrypt.c new file mode 100644 index 000000000..725a81b70 --- /dev/null +++ b/sm/encrypt.c @@ -0,0 +1,550 @@ +/* encrypt.c - Encrypt a message + * Copyright (C) 2001, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +struct dek_s { + const char *algoid; + int algo; + gcry_cipher_hd_t chd; + char key[32]; + int keylen; + char iv[32]; + int ivlen; +}; +typedef struct dek_s *DEK; + +struct encrypt_cb_parm_s { + FILE *fp; + DEK dek; + int eof_seen; + int ready; + int readerror; + int bufsize; + unsigned char *buffer; + int buflen; +}; + + + + + +/* initialize the data encryptionkey (session key) */ +static int +init_dek (DEK dek) +{ + int rc=0, mode, i; + + dek->algo = gcry_cipher_map_name (dek->algoid); + mode = gcry_cipher_mode_from_oid (dek->algoid); + if (!dek->algo || !mode) + { + log_error ("unsupported algorithm `%s'\n", dek->algoid); + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + } + + dek->keylen = gcry_cipher_get_algo_keylen (dek->algo); + if (!dek->keylen || dek->keylen > sizeof (dek->key)) + return gpg_error (GPG_ERR_BUG); + + dek->ivlen = gcry_cipher_get_algo_blklen (dek->algo); + if (!dek->ivlen || dek->ivlen > sizeof (dek->iv)) + return gpg_error (GPG_ERR_BUG); + + if (dek->keylen < 100/8) + { /* make sure we don't use weak keys */ + log_error ("key length of `%s' too small\n", dek->algoid); + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + } + + rc = gcry_cipher_open (&dek->chd, dek->algo, mode, GCRY_CIPHER_SECURE); + if (rc) + { + log_error ("failed to create cipher context: %s\n", gpg_strerror (rc)); + return rc; + } + + for (i=0; i < 8; i++) + { + gcry_randomize (dek->key, dek->keylen, GCRY_STRONG_RANDOM ); + rc = gcry_cipher_setkey (dek->chd, dek->key, dek->keylen); + if (gpg_err_code (rc) != GPG_ERR_WEAK_KEY) + break; + log_info(_("weak key created - retrying\n") ); + } + if (rc) + { + log_error ("failed to set the key: %s\n", gpg_strerror (rc)); + gcry_cipher_close (dek->chd); + dek->chd = NULL; + return rc; + } + + gcry_randomize (dek->iv, dek->ivlen, GCRY_STRONG_RANDOM); + rc = gcry_cipher_setiv (dek->chd, dek->iv, dek->ivlen); + if (rc) + { + log_error ("failed to set the IV: %s\n", gpg_strerror (rc)); + gcry_cipher_close (dek->chd); + dek->chd = NULL; + return rc; + } + + return 0; +} + + +/* Encode the session key. NBITS is the number of bits which should be + used for packing the session key. returns: An mpi with the session + key (caller must free) */ +static gcry_mpi_t +encode_session_key (DEK dek, unsigned int nbits) +{ + int nframe = (nbits+7) / 8; + byte *p; + byte *frame; + int i,n; + gcry_mpi_t a; + + if (dek->keylen + 7 > nframe || !nframe) + log_bug ("can't encode a %d bit key in a %d bits frame\n", + dek->keylen*8, nbits ); + + /* We encode the session key in this way: + * + * 0 2 RND(n bytes) 0 KEY(k bytes) + * + * (But how can we store the leading 0 - the external representaion + * of MPIs doesn't allow leading zeroes =:-) + * + * RND are non-zero random bytes. + * KEY is the encryption key (session key) + */ + + frame = gcry_xmalloc_secure (nframe); + n = 0; + frame[n++] = 0; + frame[n++] = 2; + i = nframe - 3 - dek->keylen; + assert (i > 0); + p = gcry_random_bytes_secure (i, GCRY_STRONG_RANDOM); + /* replace zero bytes by new values */ + for (;;) + { + int j, k; + byte *pp; + + /* count the zero bytes */ + for(j=k=0; j < i; j++ ) + { + if( !p[j] ) + k++; + } + if( !k ) + break; /* okay: no zero bytes */ + + k += k/128; /* better get some more */ + pp = gcry_random_bytes_secure (k, GCRY_STRONG_RANDOM); + for (j=0; j < i && k; j++) + { + if( !p[j] ) + p[j] = pp[--k]; + } + xfree (pp); + } + memcpy (frame+n, p, i); + xfree (p); + + n += i; + frame[n++] = 0; + memcpy (frame+n, dek->key, dek->keylen); + n += dek->keylen; + assert (n == nframe); + if (gcry_mpi_scan (&a, GCRYMPI_FMT_USG, frame, n, &nframe) ) + BUG (); + gcry_free(frame); + + return a; +} + + + +/* encrypt the DEK under the key contained in CERT and return it as a + canonical S-Exp in encval */ +static int +encrypt_dek (const DEK dek, KsbaCert cert, char **encval) +{ + gcry_sexp_t s_ciph, s_data, s_pkey; + int rc; + KsbaSexp buf; + size_t len; + + *encval = NULL; + + /* get the key from the cert */ + buf = ksba_cert_get_public_key (cert); + if (!buf) + { + log_error ("no public key for recipient\n"); + return gpg_error (GPG_ERR_NO_PUBKEY); + } + len = gcry_sexp_canon_len (buf, 0, NULL, NULL); + if (!len) + { + log_error ("libksba did not return a proper S-Exp\n"); + return gpg_error (GPG_ERR_BUG); + } + rc = gcry_sexp_sscan (&s_pkey, NULL, buf, len); + xfree (buf); buf = NULL; + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + return rc; + } + + /* put the encoded cleartext into a simple list */ + { + /* fixme: actually the pkcs-1 encoding should go into libgcrypt */ + gcry_mpi_t data = encode_session_key (dek, gcry_pk_get_nbits (s_pkey)); + if (!data) + { + gcry_mpi_release (data); + return gpg_error (GPG_ERR_GENERAL); + } + if (gcry_sexp_build (&s_data, NULL, "%m", data)) + BUG (); + gcry_mpi_release (data); + } + + /* pass it to libgcrypt */ + rc = gcry_pk_encrypt (&s_ciph, s_data, s_pkey); + gcry_sexp_release (s_data); + gcry_sexp_release (s_pkey); + + /* reformat it */ + len = gcry_sexp_sprint (s_ciph, GCRYSEXP_FMT_CANON, NULL, 0); + assert (len); + buf = xtrymalloc (len); + if (!buf) + { + gpg_error_t tmperr = OUT_OF_CORE (errno); + gcry_sexp_release (s_ciph); + return tmperr; + } + len = gcry_sexp_sprint (s_ciph, GCRYSEXP_FMT_CANON, buf, len); + assert (len); + + *encval = buf; + return 0; +} + + + +/* do the actual encryption */ +static int +encrypt_cb (void *cb_value, char *buffer, size_t count, size_t *nread) +{ + struct encrypt_cb_parm_s *parm = cb_value; + int blklen = parm->dek->ivlen; + unsigned char *p; + size_t n; + + *nread = 0; + if (!buffer) + return -1; /* not supported */ + + if (parm->ready) + return -1; + + if (count < blklen) + BUG (); + + if (!parm->eof_seen) + { /* fillup the buffer */ + p = parm->buffer; + for (n=parm->buflen; n < parm->bufsize; n++) + { + int c = getc (parm->fp); + if (c == EOF) + { + if (ferror (parm->fp)) + { + parm->readerror = errno; + return -1; + } + parm->eof_seen = 1; + break; + } + p[n] = c; + } + parm->buflen = n; + } + + n = parm->buflen < count? parm->buflen : count; + n = n/blklen * blklen; + if (n) + { /* encrypt the stuff */ + gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); + *nread = n; + /* Who cares about cycles, take the easy way and shift the buffer */ + parm->buflen -= n; + memmove (parm->buffer, parm->buffer+n, parm->buflen); + } + else if (parm->eof_seen) + { /* no complete block but eof: add padding */ + /* fixme: we should try to do this also in the above code path */ + int i, npad = blklen - (parm->buflen % blklen); + p = parm->buffer; + for (n=parm->buflen, i=0; n < parm->bufsize && i < npad; n++, i++) + p[n] = npad; + gcry_cipher_encrypt (parm->dek->chd, buffer, n, parm->buffer, n); + *nread = n; + parm->ready = 1; + } + + return 0; +} + + + + +/* Perform an encrypt operation. + + Encrypt the data received on DATA-FD and write it to OUT_FP. The + recipients are take from the certificate given in recplist; if this + is NULL it will be encrypted for a default recipient */ +int +gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int data_fd, FILE *out_fp) +{ + int rc = 0; + Base64Context b64writer = NULL; + KsbaError err; + KsbaWriter writer; + KsbaReader reader = NULL; + KsbaCMS cms = NULL; + KsbaStopReason stopreason; + KEYDB_HANDLE kh = NULL; + struct encrypt_cb_parm_s encparm; + DEK dek = NULL; + int recpno; + FILE *data_fp = NULL; + CERTLIST cl; + + memset (&encparm, 0, sizeof encparm); + + if (!recplist) + { + log_error(_("no valid recipients given\n")); + gpgsm_status (ctrl, STATUS_NO_RECP, "0"); + rc = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + data_fp = fdopen ( dup (data_fd), "rb"); + if (!data_fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + reader = ksba_reader_new (); + if (!reader) + rc = KSBA_Out_Of_Core; + if (!rc) + rc = ksba_reader_set_cb (reader, encrypt_cb, &encparm); + if (rc) + { + rc = map_ksba_err (rc); + goto leave; + } + encparm.fp = data_fp; + + ctrl->pem_name = "ENCRYPTED MESSAGE"; + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + cms = ksba_cms_new (); + if (!cms) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + err = ksba_cms_set_reader_writer (cms, reader, writer); + if (err) + { + log_debug ("ksba_cms_set_reader_writer failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + /* We are going to create enveloped data with uninterpreted data as + inner content */ + err = ksba_cms_set_content_type (cms, 0, KSBA_CT_ENVELOPED_DATA); + if (!err) + err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); + if (err) + { + log_debug ("ksba_cms_set_content_type failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + /* create a session key */ + dek = xtrycalloc (1, sizeof *dek); /* hmmm: should we put it into secmem?*/ + if (!dek) + rc = OUT_OF_CORE (errno); + else + { + dek->algoid = opt.def_cipher_algoid; + rc = init_dek (dek); + } + if (rc) + { + log_error ("failed to create the session key: %s\n", + gpg_strerror (rc)); + goto leave; + } + + err = ksba_cms_set_content_enc_algo (cms, dek->algoid, dek->iv, dek->ivlen); + if (err) + { + log_error ("ksba_cms_set_content_enc_algo failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + encparm.dek = dek; + /* Use a ~8k (AES) or ~4k (3DES) buffer */ + encparm.bufsize = 500 * dek->ivlen; + encparm.buffer = xtrymalloc (encparm.bufsize); + if (!encparm.buffer) + { + rc = OUT_OF_CORE (errno); + goto leave; + } + + /* gather certificates of recipients, encrypt the session key for + each and store them in the CMS object */ + for (recpno = 0, cl = recplist; cl; recpno++, cl = cl->next) + { + char *encval; + + rc = encrypt_dek (dek, cl->cert, &encval); + if (rc) + { + log_error ("encryption failed for recipient no. %d: %s\n", + recpno, gpg_strerror (rc)); + goto leave; + } + + err = ksba_cms_add_recipient (cms, cl->cert); + if (err) + { + log_error ("ksba_cms_add_recipient failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + xfree (encval); + goto leave; + } + + err = ksba_cms_set_enc_val (cms, recpno, encval); + xfree (encval); + if (err) + { + log_error ("ksba_cms_set_enc_val failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + + /* main control loop for encryption */ + recpno = 0; + do + { + err = ksba_cms_build (cms, &stopreason); + if (err) + { + log_debug ("ksba_cms_build failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + while (stopreason != KSBA_SR_READY); + + if (encparm.readerror) + { + log_error ("error reading input: %s\n", strerror (encparm.readerror)); + rc = gpg_error (gpg_err_code_from_errno (encparm.readerror)); + goto leave; + } + + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + log_info ("encrypted data created\n"); + + leave: + ksba_cms_release (cms); + gpgsm_destroy_writer (b64writer); + ksba_reader_release (reader); + keydb_release (kh); + xfree (dek); + if (data_fp) + fclose (data_fp); + xfree (encparm.buffer); + return rc; +} diff --git a/sm/export.c b/sm/export.c new file mode 100644 index 000000000..93a55debc --- /dev/null +++ b/sm/export.c @@ -0,0 +1,249 @@ +/* export.c + * Copyright (C) 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" + +static void print_short_info (KsbaCert cert, FILE *fp); + + + +/* Export all certificates or just those given in NAMES. */ +void +gpgsm_export (CTRL ctrl, STRLIST names, FILE *fp) +{ + KEYDB_HANDLE hd; + KEYDB_SEARCH_DESC *desc = NULL; + int ndesc; + Base64Context b64writer = NULL; + KsbaWriter writer; + STRLIST sl; + KsbaCert cert = NULL; + int rc=0; + int count = 0; + int i; + + hd = keydb_new (0); + if (!hd) + { + log_error ("keydb_new failed\n"); + goto leave; + } + + if (!names) + ndesc = 1; + else + { + for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) + ; + } + + desc = xtrycalloc (ndesc, sizeof *desc); + if (!ndesc) + { + log_error ("allocating memory for export failed: %s\n", + gpg_strerror (OUT_OF_CORE (errno))); + goto leave; + } + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_FIRST; + else + { + for (ndesc=0, sl=names; sl; sl = sl->next) + { + rc = keydb_classify_name (sl->d, desc+ndesc); + if (rc) + { + log_error ("key `%s' not found: %s\n", + sl->d, gpg_strerror (rc)); + rc = 0; + } + else + ndesc++; + } + } + + /* If all specifications are done by fingerprint, we switch to + ephemeral mode so that _all_ currently available and matching + certificates are exported. + + fixme: we should in this case keep a list of certificates to + avoid accidential export of duplicate certificates. */ + if (names && ndesc) + { + for (i=0; (i < ndesc + && (desc[i].mode == KEYDB_SEARCH_MODE_FPR + || desc[i].mode == KEYDB_SEARCH_MODE_FPR20 + || desc[i].mode == KEYDB_SEARCH_MODE_FPR16)); i++) + ; + if (i == ndesc) + keydb_set_ephemeral (hd, 1); + } + + while (!(rc = keydb_search (hd, desc, ndesc))) + { + const unsigned char *image; + size_t imagelen; + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_NEXT; + + rc = keydb_get_cert (hd, &cert); + if (rc) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + image = ksba_cert_get_image (cert, &imagelen); + if (!image) + { + log_error ("ksba_cert_get_image failed\n"); + goto leave; + } + + if (ctrl->create_pem) + { + if (count) + putc ('\n', fp); + print_short_info (cert, fp); + putc ('\n', fp); + } + count++; + + if (!b64writer) + { + ctrl->pem_name = "CERTIFICATE"; + rc = gpgsm_create_writer (&b64writer, ctrl, fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + rc = ksba_writer_write (writer, image, imagelen); + if (rc) + { + log_error ("write error: %s\n", ksba_strerror (rc)); + goto leave; + } + + if (ctrl->create_pem) + { + /* We want one certificate per PEM block */ + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + gpgsm_destroy_writer (b64writer); + b64writer = NULL; + } + + ksba_cert_release (cert); + cert = NULL; + } + if (rc && rc != -1) + log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); + else if (b64writer) + { + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + leave: + gpgsm_destroy_writer (b64writer); + ksba_cert_release (cert); + xfree (desc); + keydb_release (hd); +} + + +/* Print some info about the certifciate CERT to FP */ +static void +print_short_info (KsbaCert cert, FILE *fp) +{ + char *p; + KsbaSexp sexp; + int idx; + + for (idx=0; (p = ksba_cert_get_issuer (cert, idx)); idx++) + { + fputs (!idx? "Issuer ...: " + : "\n aka ...: ", fp); + gpgsm_print_name (fp, p); + xfree (p); + } + putc ('\n', fp); + + fputs ("Serial ...: ", fp); + sexp = ksba_cert_get_serial (cert); + if (sexp) + { + int len; + const unsigned char *s = sexp; + + if (*s == '(') + { + s++; + for (len=0; *s && *s != ':' && digitp (s); s++) + len = len*10 + atoi_1 (s); + if (*s == ':') + for (s++; len; len--, s++) + fprintf (fp, "%02X", *s); + } + xfree (sexp); + } + putc ('\n', fp); + + for (idx=0; (p = ksba_cert_get_subject (cert, idx)); idx++) + { + fputs (!idx? "Subject ..: " + : "\n aka ..: ", fp); + gpgsm_print_name (fp, p); + xfree (p); + } + putc ('\n', fp); +} + + + + + + diff --git a/sm/fingerprint.c b/sm/fingerprint.c new file mode 100644 index 000000000..028c08aab --- /dev/null +++ b/sm/fingerprint.c @@ -0,0 +1,271 @@ +/* fingerprint.c - Get the fingerprint + * Copyright (C) 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +/* Return the fingerprint of the certificate (we can't put this into + libksba because we need libgcrypt support). The caller must + provide an array of sufficient length or NULL so that the function + allocates the array. If r_len is not NULL, the length of the + digest is returned; well, this can also be done by using + gcry_md_get_algo_dlen(). If algo is 0, a SHA-1 will be used. + + If there is a problem , the function does never return NULL but a + digest of all 0xff. + */ +char * +gpgsm_get_fingerprint (KsbaCert cert, int algo, char *array, int *r_len) +{ + gcry_md_hd_t md; + int rc, len; + + if (!algo) + algo = GCRY_MD_SHA1; + + len = gcry_md_get_algo_dlen (algo); + assert (len); + if (!array) + array = xmalloc (len); + + if (r_len) + *r_len = len; + + rc = gcry_md_open (&md, algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + memset (array, 0xff, len); /* better return an invalid fpr than NULL */ + return array; + } + + rc = ksba_cert_hash (cert, 0, HASH_FNC, md); + if (rc) + { + log_error ("ksba_cert_hash failed: %s\n", ksba_strerror (rc)); + gcry_md_close (md); + memset (array, 0xff, len); /* better return an invalid fpr than NULL */ + return array; + } + gcry_md_final (md); + memcpy (array, gcry_md_read(md, algo), len ); + return array; +} + + +/* Return an allocated buffer with the formatted fingerprint */ +char * +gpgsm_get_fingerprint_string (KsbaCert cert, int algo) +{ + unsigned char digest[MAX_DIGEST_LEN]; + char *buf; + int len, i; + + if (!algo) + algo = GCRY_MD_SHA1; + + len = gcry_md_get_algo_dlen (algo); + assert (len <= MAX_DIGEST_LEN ); + gpgsm_get_fingerprint (cert, algo, digest, NULL); + buf = xmalloc (len*3+1); + *buf = 0; + for (i=0; i < len; i++ ) + sprintf (buf+strlen(buf), i? ":%02X":"%02X", digest[i]); + return buf; +} + +/* Return an allocated buffer with the formatted fingerprint as one + large hexnumber */ +char * +gpgsm_get_fingerprint_hexstring (KsbaCert cert, int algo) +{ + unsigned char digest[MAX_DIGEST_LEN]; + char *buf; + int len, i; + + if (!algo) + algo = GCRY_MD_SHA1; + + len = gcry_md_get_algo_dlen (algo); + assert (len <= MAX_DIGEST_LEN ); + gpgsm_get_fingerprint (cert, algo, digest, NULL); + buf = xmalloc (len*3+1); + *buf = 0; + for (i=0; i < len; i++ ) + sprintf (buf+strlen(buf), "%02X", digest[i]); + return buf; +} + +/* Return a certificate ID. These are the last 4 bytes of the SHA-1 + fingerprint. */ +unsigned long +gpgsm_get_short_fingerprint (KsbaCert cert) +{ + unsigned char digest[20]; + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); + return ((digest[16]<<24)|(digest[17]<<16)|(digest[18]<< 8)|digest[19]); +} + + +/* Return the so called KEYGRIP which is the SHA-1 hash of the public + key parameters expressed as an canoncial encoded S-Exp. array must + be 20 bytes long. returns the array or a newly allocated one if the + passed one was NULL */ +char * +gpgsm_get_keygrip (KsbaCert cert, char *array) +{ + gcry_sexp_t s_pkey; + int rc; + KsbaSexp p; + size_t n; + + p = ksba_cert_get_public_key (cert); + if (!p) + return NULL; /* oops */ + + if (DBG_X509) + log_debug ("get_keygrip for public key: %s\n", p); + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + { + log_error ("libksba did not return a proper S-Exp\n"); + return NULL; + } + rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + xfree (p); + if (rc) + { + log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc)); + return NULL; + } + array = gcry_pk_get_keygrip (s_pkey, array); + gcry_sexp_release (s_pkey); + if (!array) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error ("can't calculate keygrip\n"); + return NULL; + } + if (DBG_X509) + log_printhex ("keygrip=", array, 20); + + return array; +} + +/* Return an allocated buffer with the keygrip of CERT in from of an + hexstring. NULL is returned in case of error */ +char * +gpgsm_get_keygrip_hexstring (KsbaCert cert) +{ + unsigned char grip[20]; + char *buf, *p; + int i; + + gpgsm_get_keygrip (cert, grip); + buf = p = xmalloc (20*2+1); + for (i=0; i < 20; i++, p += 2 ) + sprintf (p, "%02X", grip[i]); + return buf; +} + + + +/* For certain purposes we need a certificate id which has an upper + limit of the size. We use the hash of the issuer name and the + serial number for this. In most cases the serial number is not + that large and the resulting string can be passed on an assuan + command line. Everything is hexencoded with the serialnumber + delimted from the has by a dot. + + The caller must free the string. +*/ +char * +gpgsm_get_certid (KsbaCert cert) +{ + KsbaSexp serial; + unsigned char *p; + char *endp; + unsigned char hash[20]; + unsigned long n; + char *certid; + int i; + + p = ksba_cert_get_issuer (cert, 0); + if (!p) + return NULL; /* Ooops: No issuer */ + gcry_md_hash_buffer (GCRY_MD_SHA1, hash, p, strlen (p)); + xfree (p); + + serial = ksba_cert_get_serial (cert); + if (!serial) + return NULL; /* oops: no serial number */ + p = serial; + if (*p != '(') + { + log_error ("Ooops: invalid serial number\n"); + xfree (serial); + return NULL; + } + p++; + n = strtoul (p, &endp, 10); + p = endp; + if (*p != ':') + { + log_error ("Ooops: invalid serial number (no colon)\n"); + xfree (serial); + return NULL; + } + p++; + + certid = xtrymalloc ( 40 + 1 + n*2 + 1); + if (!certid) + { + xfree (serial); + return NULL; /* out of core */ + } + + for (i=0, endp = certid; i < 20; i++, endp += 2 ) + sprintf (endp, "%02X", hash[i]); + *endp++ = '.'; + for (i=0; i < n; i++, endp += 2) + sprintf (endp, "%02X", p[i]); + *endp = 0; + + xfree (serial); + return certid; +} + + + + + + diff --git a/sm/gpgsm.c b/sm/gpgsm.c new file mode 100644 index 000000000..c392886ba --- /dev/null +++ b/sm/gpgsm.c @@ -0,0 +1,1458 @@ +/* gpgsm.c - GnuPG for S/MIME + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <fcntl.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <assuan.h> /* malloc hooks */ + +#include "../kbx/keybox.h" /* malloc hooks */ +#include "i18n.h" +#include "keydb.h" +#include "sysutils.h" + +enum cmd_and_opt_values { + aNull = 0, + oArmor = 'a', + aDetachedSign = 'b', + aSym = 'c', + aDecrypt = 'd', + aEncr = 'e', + oInteractive = 'i', + oKOption = 'k', + oDryRun = 'n', + oOutput = 'o', + oQuiet = 'q', + oRecipient = 'r', + aSign = 's', + oTextmodeShort= 't', + oUser = 'u', + oVerbose = 'v', + oCompress = 'z', + oNotation = 'N', + oBatch = 500, + aClearsign, + aStore, + aKeygen, + aSignEncr, + aSignKey, + aLSignKey, + aListPackets, + aEditKey, + aDeleteKey, + aImport, + aVerify, + aVerifyFiles, + aListKeys, + aListExternalKeys, + aListSigs, + aListSecretKeys, + aSendKeys, + aRecvKeys, + aExport, + aCheckKeys, /* nyi */ + aServer, + aLearnCard, + aCallDirmngr, + aCallProtectTool, + aPasswd, + + oOptions, + oDebug, + oDebugAll, + oDebugWait, + oDebugNoChainValidation, + oLogFile, + + oEnableSpecialFilenames, + + oAgentProgram, + oDisplay, + oTTYname, + oTTYtype, + oLCctype, + oLCmessages, + + oDirmngrProgram, + oFakedSystemTime, + + + oAssumeArmor, + oAssumeBase64, + oAssumeBinary, + + oBase64, + oNoArmor, + + oDisableCRLChecks, + oEnableCRLChecks, + + oIncludeCerts, + oPolicyFile, + oDisablePolicyChecks, + oEnablePolicyChecks, + oAutoIssuerKeyRetrieve, + + + oTextmode, + oFingerprint, + oWithFingerprint, + oAnswerYes, + oAnswerNo, + oKeyring, + oSecretKeyring, + oDefaultKey, + oDefRecipient, + oDefRecipientSelf, + oNoDefRecipient, + oStatusFD, + oNoComment, + oNoVersion, + oEmitVersion, + oCompletesNeeded, + oMarginalsNeeded, + oMaxCertDepth, + oLoadExtension, + oRFC1991, + oOpenPGP, + oCipherAlgo, + oDigestAlgo, + oCompressAlgo, + oCommandFD, + oNoVerbose, + oTrustDBName, + oNoSecmemWarn, + oNoDefKeyring, + oNoGreeting, + oNoTTY, + oNoOptions, + oNoBatch, + oHomedir, + oWithColons, + oWithKeyData, + oSkipVerify, + oCompressKeys, + oCompressSigs, + oAlwaysTrust, + oRunAsShmCP, + oSetFilename, + oSetPolicyURL, + oUseEmbeddedFilename, + oComment, + oDefaultComment, + oThrowKeyid, + oForceV3Sigs, + oForceMDC, + oS2KMode, + oS2KDigest, + oS2KCipher, + oCharset, + oNotDashEscaped, + oEscapeFrom, + oLockOnce, + oLockMultiple, + oLockNever, + oKeyServer, + oEncryptTo, + oNoEncryptTo, + oLoggerFD, + oUtf8Strings, + oNoUtf8Strings, + oDisableCipherAlgo, + oDisablePubkeyAlgo, + oAllowNonSelfsignedUID, + oAllowFreeformUID, + oNoLiteral, + oSetFilesize, + oHonorHttpProxy, + oFastListMode, + oListOnly, + oIgnoreTimeConflict, + oNoRandomSeedFile, + oNoAutoKeyRetrieve, + oUseAgent, + oMergeOnly, + oTryAllSecrets, + oTrustedKey, + oEmuMDEncodeBug, + aDummy + }; + + +static ARGPARSE_OPTS opts[] = { + + { 300, NULL, 0, N_("@Commands:\n ") }, + + { aSign, "sign", 256, N_("|[file]|make a signature")}, + { aClearsign, "clearsign", 256, N_("|[file]|make a clear text signature") }, + { aDetachedSign, "detach-sign", 256, N_("make a detached signature")}, + { aEncr, "encrypt", 256, N_("encrypt data")}, + { aSym, "symmetric", 256, N_("encryption only with symmetric cipher")}, + { aDecrypt, "decrypt", 256, N_("decrypt data (default)")}, + { aVerify, "verify" , 256, N_("verify a signature")}, + { aVerifyFiles, "verify-files" , 256, "@" }, + { aListKeys, "list-keys", 256, N_("list keys")}, + { aListExternalKeys, "list-external-keys", 256, N_("list external keys")}, + { aListSecretKeys, "list-secret-keys", 256, N_("list secret keys")}, + { aListSigs, "list-sigs", 256, N_("list certificate chain")}, + { aListSigs, "check-sigs",256, "@"}, + { oFingerprint, "fingerprint", 256, N_("list keys and fingerprints")}, + { aKeygen, "gen-key", 256, N_("generate a new key pair")}, + { aDeleteKey, "delete-key",256, N_("remove key from the public keyring")}, + { aSendKeys, "send-keys" , 256, N_("export keys to a key server") }, + { aRecvKeys, "recv-keys" , 256, N_("import keys from a key server") }, + { aImport, "import", 256 , N_("import certificates")}, + { aExport, "export", 256 , N_("export certificates")}, + { aLearnCard, "learn-card", 256 ,N_("register a smartcard")}, + { aServer, "server", 256, N_("run in server mode")}, + { aCallDirmngr, "call-dirmngr", 256, N_("pass a command to the dirmngr")}, + { aCallProtectTool, "call-protect-tool", 256, + N_("invoke gpg-protect-tool")}, + { aPasswd, "passwd", 256, N_("change a passphrase")}, + + { 301, NULL, 0, N_("@\nOptions:\n ") }, + + { oArmor, "armor", 0, N_("create ascii armored output")}, + { oArmor, "armour", 0, "@" }, + { oBase64, "base64", 0, N_("create base-64 encoded output")}, + + { oAssumeArmor, "assume-armor", 0, N_("assume input is in PEM format")}, + { oAssumeBase64, "assume-base64", 0, + N_("assume input is in base-64 format")}, + { oAssumeBinary, "assume-binary", 0, + N_("assume input is in binary format")}, + + { oRecipient, "recipient", 2, N_("|NAME|encrypt for NAME")}, + + + { oDisableCRLChecks, "disable-crl-checks", 0, N_("never consult a CRL")}, + { oEnableCRLChecks, "enable-crl-checks", 0, "@"}, + + { oIncludeCerts, "include-certs", 1, + N_("|N|number of certificates to include") }, + + { oPolicyFile, "policy-file", 2, + N_("|FILE|take policy information from FILE") }, + + { oDisablePolicyChecks, "disable-policy-checks", 0, + N_("do not check certificate policies")}, + { oEnablePolicyChecks, "enable-policy-checks", 0, "@"}, + + { oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve", 0, + N_("fetch missing issuer certificates")}, + +#if 0 + { oDefRecipient, "default-recipient" ,2, + N_("|NAME|use NAME as default recipient")}, + { oDefRecipientSelf, "default-recipient-self" ,0, + N_("use the default key as default recipient")}, + { oNoDefRecipient, "no-default-recipient", 0, "@" }, + { oEncryptTo, "encrypt-to", 2, "@" }, + { oNoEncryptTo, "no-encrypt-to", 0, "@" }, + +#endif + { oUser, "local-user",2, N_("use this user-id to sign or decrypt")}, + +#if 0 + { oCompress, NULL, 1, N_("|N|set compress level N (0 disables)") }, + { oTextmodeShort, NULL, 0, "@"}, + { oTextmode, "textmode", 0, N_("use canonical text mode")}, +#endif + + { oOutput, "output", 2, N_("use as output file")}, + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oNoTTY, "no-tty", 0, N_("don't use the terminal at all") }, + { oLogFile, "log-file" ,2, N_("use a log file for the server")}, +#if 0 + { oForceV3Sigs, "force-v3-sigs", 0, N_("force v3 signatures") }, + { oForceMDC, "force-mdc", 0, N_("always use a MDC for encryption") }, +#endif + { oDryRun, "dry-run", 0, N_("do not make any changes") }, + /*{ oInteractive, "interactive", 0, N_("prompt before overwriting") }, */ + /*{ oUseAgent, "use-agent",0, N_("use the gpg-agent")},*/ + { oBatch, "batch", 0, N_("batch mode: never ask")}, + { oAnswerYes, "yes", 0, N_("assume yes on most questions")}, + { oAnswerNo, "no", 0, N_("assume no on most questions")}, + + { oKeyring, "keyring" ,2, N_("add this keyring to the list of keyrings")}, + { oSecretKeyring, "secret-keyring" ,2, N_("add this secret keyring to the list")}, + { oDefaultKey, "default-key" ,2, N_("|NAME|use NAME as default secret key")}, + { oKeyServer, "keyserver",2, N_("|HOST|use this keyserver to lookup keys")}, + { oCharset, "charset" , 2, N_("|NAME|set terminal charset to NAME") }, + { oOptions, "options" , 2, N_("read options from file")}, + + { oDebug, "debug" ,4|16, "@"}, + { oDebugAll, "debug-all" ,0, "@"}, + { oDebugWait, "debug-wait" ,1, "@"}, + { oDebugNoChainValidation, "debug-no-chain-validation" ,0, "@"}, + { oStatusFD, "status-fd" ,1, N_("|FD|write status info to this FD") }, + { aDummy, "no-comment", 0, "@"}, + { aDummy, "completes-needed", 1, "@"}, + { aDummy, "marginals-needed", 1, "@"}, + { oMaxCertDepth, "max-cert-depth", 1, "@" }, + { aDummy, "trusted-key", 2, "@"}, + { oLoadExtension, "load-extension" ,2, + N_("|FILE|load extension module FILE")}, + { aDummy, "rfc1991", 0, "@"}, + { aDummy, "openpgp", 0, "@"}, + { aDummy, "s2k-mode", 1, "@"}, + { aDummy, "s2k-digest-algo",2, "@"}, + { aDummy, "s2k-cipher-algo",2, "@"}, + { oCipherAlgo, "cipher-algo", 2 , N_("|NAME|use cipher algorithm NAME")}, + { oDigestAlgo, "digest-algo", 2 , + N_("|NAME|use message digest algorithm NAME")}, +#if 0 + { oCompressAlgo, "compress-algo", 1 , N_("|N|use compress algorithm N")}, +#endif + { aDummy, "throw-keyid", 0, "@"}, + { aDummy, "notation-data", 2, "@"}, + + { 302, NULL, 0, N_( + "@\n(See the man page for a complete listing of all commands and options)\n" + )}, + + { 303, NULL, 0, N_("@\nExamples:\n\n" + " -se -r Bob [file] sign and encrypt for user Bob\n" + " --clearsign [file] make a clear text signature\n" + " --detach-sign [file] make a detached signature\n" + " --list-keys [names] show keys\n" + " --fingerprint [names] show fingerprints\n" ) }, + + /* hidden options */ + { oNoVerbose, "no-verbose", 0, "@"}, + + { oEnableSpecialFilenames, "enable-special-filenames", 0, "@" }, + + + { oTrustDBName, "trustdb-name", 2, "@" }, + { oNoSecmemWarn, "no-secmem-warning", 0, "@" }, + { oNoArmor, "no-armor", 0, "@"}, + { oNoArmor, "no-armour", 0, "@"}, + { oNoDefKeyring, "no-default-keyring", 0, "@" }, + { oNoGreeting, "no-greeting", 0, "@" }, + { oNoOptions, "no-options", 0, "@" }, /* shortcut for --options /dev/null */ + { oHomedir, "homedir", 2, "@" }, /* defaults to "~/.gnupg" */ + { oAgentProgram, "agent-program", 2 , "@" }, + { oDisplay, "display", 2, "@" }, + { oTTYname, "ttyname", 2, "@" }, + { oTTYtype, "ttytype", 2, "@" }, + { oLCctype, "lc-ctype", 2, "@" }, + { oLCmessages, "lc-messages", 2, "@" }, + { oDirmngrProgram, "dirmngr-program", 2 , "@" }, + { oFakedSystemTime, "faked-system-time", 4, "@" }, /* (epoch time) */ + + + { oNoBatch, "no-batch", 0, "@" }, + { oWithColons, "with-colons", 0, "@"}, + { oWithKeyData,"with-key-data", 0, "@"}, + { aListKeys, "list-key", 0, "@" }, /* alias */ + { aListSigs, "list-sig", 0, "@" }, /* alias */ + { aListSigs, "check-sig",0, "@" }, /* alias */ + { oSkipVerify, "skip-verify",0, "@" }, + { oCompressKeys, "compress-keys",0, "@"}, + { oCompressSigs, "compress-sigs",0, "@"}, + { oAlwaysTrust, "always-trust", 0, "@"}, + { oNoVersion, "no-version", 0, "@"}, + { oLockOnce, "lock-once", 0, "@" }, + { oLockMultiple, "lock-multiple", 0, "@" }, + { oLockNever, "lock-never", 0, "@" }, + { oLoggerFD, "logger-fd",1, "@" }, + { oWithFingerprint, "with-fingerprint", 0, "@" }, + { oDisableCipherAlgo, "disable-cipher-algo", 2, "@" }, + { oDisablePubkeyAlgo, "disable-pubkey-algo", 2, "@" }, + { oHonorHttpProxy,"honor-http-proxy", 0, "@" }, + { oListOnly, "list-only", 0, "@"}, + { oIgnoreTimeConflict, "ignore-time-conflict", 0, "@" }, + { oNoRandomSeedFile, "no-random-seed-file", 0, "@" }, +{0} }; + + + +int gpgsm_errors_seen = 0; + +/* It is possible that we are currentlu running under setuid permissions */ +static int maybe_setuid = 1; + +/* Option --enable-special-filenames */ +static int allow_special_filenames; + + +static char *build_list (const char *text, + const char *(*mapf)(int), int (*chkf)(int)); +static void set_cmd (enum cmd_and_opt_values *ret_cmd, + enum cmd_and_opt_values new_cmd ); + +static void emergency_cleanup (void); +static int check_special_filename (const char *fname); +static int open_read (const char *filename); +static FILE *open_fwrite (const char *filename); +static void run_protect_tool (int argc, char **argv); + + +static int +our_pk_test_algo (int algo) +{ + return 1; +} + +static int +our_cipher_test_algo (int algo) +{ + return 1; +} + +static int +our_md_test_algo (int algo) +{ + return 1; +} + +static const char * +my_strusage( int level ) +{ + static char *digests, *pubkeys, *ciphers; + const char *p; + + switch (level) + { + case 11: p = "gpgsm (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: p = _("Usage: gpgsm [options] [files] (-h for help)"); + break; + case 41: + p = _("Syntax: gpgsm [options] [files]\n" + "sign, check, encrypt or decrypt using the S/MIME protocol\n" + "default operation depends on the input data\n"); + break; + + case 31: p = "\nHome: "; break; + case 32: p = opt.homedir; break; + case 33: p = _("\nSupported algorithms:\n"); break; + case 34: + if (!ciphers) + ciphers = build_list ("Cipher: ", gcry_cipher_algo_name, + our_cipher_test_algo ); + p = ciphers; + break; + case 35: + if (!pubkeys) + pubkeys = build_list ("Pubkey: ", gcry_pk_algo_name, + our_pk_test_algo ); + p = pubkeys; + break; + case 36: + if (!digests) + digests = build_list("Hash: ", gcry_md_algo_name, our_md_test_algo ); + p = digests; + break; + + default: p = NULL; break; + } + return p; +} + + +static char * +build_list (const char *text, const char * (*mapf)(int), int (*chkf)(int)) +{ + int i; + size_t n=strlen(text)+2; + char *list, *p; + + if (maybe_setuid) { + gcry_control (GCRYCTL_DROP_PRIVS); /* drop setuid */ + } + + for (i=1; i < 110; i++ ) + if (!chkf(i)) + n += strlen(mapf(i)) + 2; + list = xmalloc (21 + n); + *list = 0; + for (p=NULL, i=1; i < 110; i++) + { + if (!chkf(i)) + { + if( !p ) + p = stpcpy (list, text ); + else + p = stpcpy (p, ", "); + p = stpcpy (p, mapf(i) ); + } + } + if (p) + p = stpcpy(p, "\n" ); + return list; +} + + +static void +i18n_init(void) +{ +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file (PACKAGE); +#else +# ifdef ENABLE_NLS +# ifdef HAVE_LC_MESSAGES + setlocale (LC_TIME, ""); + setlocale (LC_MESSAGES, ""); +# else + setlocale (LC_ALL, "" ); +# endif + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); +# endif +#endif +} + + +static void +wrong_args (const char *text) +{ + fputs (_("usage: gpgsm [options] "), stderr); + fputs (text, stderr); + putc ('\n', stderr); + gpgsm_exit (2); +} + + +static void +set_debug(void) +{ + if (opt.debug & DBG_MPI_VALUE) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); + if (opt.debug & DBG_CRYPTO_VALUE ) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); +} + + +static void +set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd) +{ + enum cmd_and_opt_values cmd = *ret_cmd; + + if (!cmd || cmd == new_cmd) + cmd = new_cmd; + else if ( cmd == aSign && new_cmd == aEncr ) + cmd = aSignEncr; + else if ( cmd == aEncr && new_cmd == aSign ) + cmd = aSignEncr; + else if ( (cmd == aSign && new_cmd == aClearsign) + || (cmd == aClearsign && new_cmd == aSign) ) + cmd = aClearsign; + else + { + log_error(_("conflicting commands\n")); + gpgsm_exit(2); + } + + *ret_cmd = cmd; +} + + +int +main ( int argc, char **argv) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + char **orig_argv; + const char *fname; + /* char *username;*/ + int may_coredump; + STRLIST sl, remusr= NULL, locusr=NULL; + STRLIST nrings=NULL; + int detached_sig = 0; + FILE *configfp = NULL; + char *configname = NULL; + unsigned configlineno; + int parse_debug = 0; + int no_more_options = 0; + int default_config =1; + int default_keyring = 1; + char *logfile = NULL; + int greeting = 0; + int nogreeting = 0; + int debug_wait = 0; + int use_random_seed = 1; + int with_fpr = 0; + char *def_digest_string = NULL; + enum cmd_and_opt_values cmd = 0; + struct server_control_s ctrl; + CERTLIST recplist = NULL; + CERTLIST signerlist = NULL; + + /* trap_unaligned ();*/ + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + /* We don't need any locking in libgcrypt unless we use any kind of + threading. */ + gcry_control (GCRYCTL_DISABLE_INTERNAL_LOCKING); + + /* Please note that we may running SUID(ROOT), so be very CAREFUL + when adding any stuff between here and the call to secmem_init() + somewhere after the option parsing */ + log_set_prefix ("gpgsm", 1); + /* check that the libraries are suitable. Do it here because the + option parse may need services of the library */ + if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + { + log_fatal( _("libgcrypt is too old (need %s, have %s)\n"), + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + } + if (!ksba_check_version (NEED_KSBA_VERSION) ) + { + log_fatal( _("libksba is too old (need %s, have %s)\n"), + NEED_KSBA_VERSION, ksba_check_version (NULL) ); + } + + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + may_coredump = disable_core_dumps (); + + gnupg_init_signals (0, emergency_cleanup); + + create_dotlock (NULL); /* register locking cleanup */ + i18n_init(); + + opt.def_cipher_algoid = "1.2.840.113549.3.7"; /*des-EDE3-CBC*/ +#ifdef __MINGW32__ + opt.homedir = read_w32_registry_string ( NULL, + "Software\\GNU\\GnuPG", "HomeDir" ); +#else + opt.homedir = getenv ("GNUPGHOME"); +#endif + if (!opt.homedir || !*opt.homedir ) + opt.homedir = GNUPG_DEFAULT_HOMEDIR; + + /* first check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ + while (arg_parse( &pargs, opts)) + { + if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) + parse_debug++; + else if (pargs.r_opt == oOptions) + { /* yes there is one, so we do not try the default one but + read the config file when it is encountered at the + commandline */ + default_config = 0; + } + else if (pargs.r_opt == oNoOptions) + default_config = 0; /* --no-options */ + else if (pargs.r_opt == oHomedir) + opt.homedir = pargs.r.ret_str; + else if (pargs.r_opt == aCallProtectTool) + break; /* This break makes sure that --version and --help are + passed to the protect-tool. */ + } + + + /* initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + maybe_setuid = 0; + + /* + Now we are now working under our real uid + */ + + ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); + assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + keybox_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + + /* Setup a default control structure for command line mode */ + memset (&ctrl, 0, sizeof ctrl); + gpgsm_init_default_ctrl (&ctrl); + ctrl.no_server = 1; + ctrl.status_fd = -1; /* not status output */ + ctrl.autodetect_encoding = 1; + + /* set the default option file */ + if (default_config ) + configname = make_filename (opt.homedir, "gpgsm.conf", NULL); + /* cet the default policy file */ + opt.policy_file = make_filename (opt.homedir, "policies.txt", NULL); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags = 1; /* do not remove the args */ + + next_pass: + if (configname) { + configlineno = 0; + configfp = fopen (configname, "r"); + if (!configfp) + { + if (default_config) + { + if (parse_debug) + log_info (_("NOTE: no default option file `%s'\n"), configname); + } + else + { + log_error (_("option file `%s': %s\n"), configname, strerror(errno)); + gpgsm_exit(2); + } + xfree(configname); + configname = NULL; + } + if (parse_debug && configname) + log_info (_("reading options from `%s'\n"), configname); + default_config = 0; + } + + while (!no_more_options + && optfile_parse (configfp, configname, &configlineno, &pargs, opts)) + { + switch (pargs.r_opt) + { + case aServer: + opt.batch = 1; + set_cmd (&cmd, aServer); + break; + case aCallDirmngr: + opt.batch = 1; + set_cmd (&cmd, aCallDirmngr); + break; + + case aCallProtectTool: + opt.batch = 1; + set_cmd (&cmd, aCallProtectTool); + no_more_options = 1; /* Stop parsing. */ + break; + + case aCheckKeys: set_cmd (&cmd, aCheckKeys); break; + case aImport: set_cmd (&cmd, aImport); break; + case aSendKeys: set_cmd (&cmd, aSendKeys); break; + case aRecvKeys: set_cmd (&cmd, aRecvKeys); break; + case aExport: set_cmd (&cmd, aExport); break; + case aListKeys: set_cmd (&cmd, aListKeys); break; + case aListExternalKeys: set_cmd (&cmd, aListExternalKeys); break; + case aListSecretKeys: set_cmd (&cmd, aListSecretKeys); break; + case aListSigs: set_cmd (&cmd, aListSigs); break; + + case aLearnCard: set_cmd (&cmd, aLearnCard); break; + + case aPasswd: set_cmd (&cmd, aPasswd); break; + + case aDeleteKey: + set_cmd (&cmd, aDeleteKey); + /*greeting=1;*/ + break; + + case aDetachedSign: + detached_sig = 1; + set_cmd (&cmd, aSign ); + break; + + case aSym: set_cmd (&cmd, aSym); break; + case aDecrypt: set_cmd (&cmd, aDecrypt); break; + case aEncr: set_cmd (&cmd, aEncr); break; + case aSign: set_cmd (&cmd, aSign ); break; + case aKeygen: set_cmd (&cmd, aKeygen); greeting=1; break; + case aClearsign: set_cmd (&cmd, aClearsign); break; + case aVerify: set_cmd (&cmd, aVerify); break; + + + /* output encoding selection */ + case oArmor: + ctrl.create_pem = 1; + break; + case oBase64: + ctrl.create_pem = 0; + ctrl.create_base64 = 1; + break; + case oNoArmor: + ctrl.create_pem = 0; + ctrl.create_base64 = 0; + break; + + /* Input encoding selection */ + case oAssumeArmor: + ctrl.autodetect_encoding = 0; + ctrl.is_pem = 1; + ctrl.is_base64 = 0; + break; + case oAssumeBase64: + ctrl.autodetect_encoding = 0; + ctrl.is_pem = 0; + ctrl.is_base64 = 1; + break; + case oAssumeBinary: + ctrl.autodetect_encoding = 0; + ctrl.is_pem = 0; + ctrl.is_base64 = 0; + break; + + case oDisableCRLChecks: + opt.no_crl_check = 1; + break; + case oEnableCRLChecks: + opt.no_crl_check = 0; + break; + + case oIncludeCerts: ctrl.include_certs = pargs.r.ret_int; break; + + case oPolicyFile: + xfree (opt.policy_file); + if (*pargs.r.ret_str) + opt.policy_file = xstrdup (pargs.r.ret_str); + else + opt.policy_file = NULL; + break; + + case oDisablePolicyChecks: + opt.no_policy_check = 1; + break; + case oEnablePolicyChecks: + opt.no_policy_check = 0; + break; + + case oAutoIssuerKeyRetrieve: + opt.auto_issuer_key_retrieve = 1; + break; + + case oOutput: opt.outfile = pargs.r.ret_str; break; + + + case oQuiet: opt.quiet = 1; break; + case oNoTTY: /* fixme:tty_no_terminal(1);*/ break; + case oDryRun: opt.dry_run = 1; break; + + case oVerbose: + opt.verbose++; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + break; + case oNoVerbose: + opt.verbose = 0; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + break; + + case oLogFile: logfile = pargs.r.ret_str; break; + + case oBatch: + opt.batch = 1; + greeting = 0; + break; + case oNoBatch: opt.batch = 0; break; + + case oAnswerYes: opt.answer_yes = 1; break; + case oAnswerNo: opt.answer_no = 1; break; + + case oKeyring: append_to_strlist (&nrings, pargs.r.ret_str); break; + + case oDebug: opt.debug |= pargs.r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oDebugNoChainValidation: opt.no_chain_validation = 1; break; + + case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break; + case oLoggerFD: log_set_fd (pargs.r.ret_int ); break; + case oWithFingerprint: + with_fpr=1; /*fall thru*/ + case oFingerprint: + opt.fingerprint++; + break; + + case oOptions: + /* config files may not be nested (silently ignore them) */ + if (!configfp) + { + xfree(configname); + configname = xstrdup (pargs.r.ret_str); + goto next_pass; + } + break; + case oNoOptions: break; /* no-options */ + case oHomedir: opt.homedir = pargs.r.ret_str; break; + case oAgentProgram: opt.agent_program = pargs.r.ret_str; break; + case oDisplay: opt.display = xstrdup (pargs.r.ret_str); break; + case oTTYname: opt.ttyname = xstrdup (pargs.r.ret_str); break; + case oTTYtype: opt.ttytype = xstrdup (pargs.r.ret_str); break; + case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break; + case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break; + case oDirmngrProgram: opt.dirmngr_program = pargs.r.ret_str; break; + + case oFakedSystemTime: + gnupg_set_time ( (time_t)pargs.r.ret_ulong, 0); + break; + + case oNoDefKeyring: default_keyring = 0; break; + case oNoGreeting: nogreeting = 1; break; + + case oDefaultKey: + /* fixme:opt.def_secret_key = pargs.r.ret_str;*/ + break; + case oDefRecipient: + if (*pargs.r.ret_str) + opt.def_recipient = xstrdup (pargs.r.ret_str); + break; + case oDefRecipientSelf: + xfree (opt.def_recipient); + opt.def_recipient = NULL; + opt.def_recipient_self = 1; + break; + case oNoDefRecipient: + xfree (opt.def_recipient); + opt.def_recipient = NULL; + opt.def_recipient_self = 0; + break; + + case oWithKeyData: opt.with_key_data=1; /* fall thru */ + case oWithColons: ctrl.with_colons = 1; break; + + case oSkipVerify: opt.skip_verify=1; break; + + case oNoEncryptTo: /*fixme: opt.no_encrypt_to = 1;*/ break; + case oEncryptTo: /* store the recipient in the second list */ + sl = add_to_strlist (&remusr, pargs.r.ret_str); + sl->flags = 1; + break; + + case oRecipient: /* store the recipient */ + add_to_strlist ( &remusr, pargs.r.ret_str); + break; + + case oTextmodeShort: /*fixme:opt.textmode = 2;*/ break; + case oTextmode: /*fixme:opt.textmode=1;*/ break; + + case oUser: /* store the local users, the first one is the default */ + if (!opt.local_user) + opt.local_user = pargs.r.ret_str; + add_to_strlist (&locusr, pargs.r.ret_str); + break; + + case oNoSecmemWarn: + gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); + break; + + case oCipherAlgo: + opt.def_cipher_algoid = pargs.r.ret_str; + break; + + case oDisableCipherAlgo: + { + int algo = gcry_cipher_map_name (pargs.r.ret_str); + gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo); + } + break; + case oDisablePubkeyAlgo: + { + int algo = gcry_pk_map_name (pargs.r.ret_str); + gcry_pk_ctl (GCRYCTL_DISABLE_ALGO,&algo, sizeof algo ); + } + break; + + case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break; + case oNoRandomSeedFile: use_random_seed = 0; break; + + case oEnableSpecialFilenames: allow_special_filenames =1; break; + + + case aDummy: + break; + default: + pargs.err = configfp? 1:2; + break; + } + } + + if (configfp) + { + fclose (configfp); + configfp = NULL; + xfree (configname); + configname = NULL; + goto next_pass; + } + + xfree (configname); + configname = NULL; + + if (log_get_errorcount(0)) + gpgsm_exit(2); + + if (nogreeting) + greeting = 0; + + if (greeting) + { + fprintf(stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + fprintf(stderr, "%s\n", strusage(15) ); + } +# ifdef IS_DEVELOPMENT_VERSION + if (!opt.batch) + { + log_info ("NOTE: THIS IS A DEVELOPMENT VERSION!\n"); + log_info ("It is only intended for test purposes and should NOT be\n"); + log_info ("used in a production environment or with production keys!\n"); + } +# endif + + if (may_coredump && !opt.quiet) + log_info (_("WARNING: program may create a core file!\n")); + + if (logfile && cmd == aServer) + { + log_set_file (logfile); + log_set_prefix (NULL, 1|2|4); + } + + if (gnupg_faked_time_p ()) + { + log_info (_("WARNING: running with faked system time: ")); + gpgsm_dump_time (gnupg_get_time ()); + log_printf ("\n"); + } + +/*FIXME if (opt.batch) */ +/* tty_batchmode (1); */ + + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + + set_debug (); + + /* FIXME: should set filenames of libgcrypt explicitly + * gpg_opt_homedir = opt.homedir; */ + + /* must do this after dropping setuid, because the mapping functions + may try to load an module and we may have disabled an algorithm */ + if ( !gcry_cipher_map_name (opt.def_cipher_algoid) + || !gcry_cipher_mode_from_oid (opt.def_cipher_algoid)) + log_error (_("selected cipher algorithm is invalid\n")); + + if (def_digest_string) + { + opt.def_digest_algo = gcry_md_map_name (def_digest_string); + xfree (def_digest_string); + def_digest_string = NULL; + if (our_md_test_algo(opt.def_digest_algo) ) + log_error (_("selected digest algorithm is invalid\n")); + } + + if (log_get_errorcount(0)) + gpgsm_exit(2); + + /* set the random seed file */ + if (use_random_seed) { + char *p = make_filename (opt.homedir, "random_seed", NULL); + gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p); + xfree(p); + } + + + if (!cmd && opt.fingerprint && !with_fpr) + set_cmd (&cmd, aListKeys); + + if (!nrings && default_keyring) /* add default keybox */ + keydb_add_resource ("pubring.kbx", 0, 0); + for (sl = nrings; sl; sl = sl->next) + keydb_add_resource (sl->d, 0, 0); + FREE_STRLIST(nrings); + + + for (sl = locusr; sl; sl = sl->next) + { + int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 1, &signerlist); + if (rc) + { + log_error (_("can't sign using `%s': %s\n"), + sl->d, gpg_strerror (rc)); + gpgsm_status2 (&ctrl, STATUS_INV_RECP, + gpg_err_code (rc) == -1? "1": + gpg_err_code (rc) == GPG_ERR_NO_PUBKEY? "1": + gpg_err_code (rc) == GPG_ERR_AMBIGUOUS_NAME? "2": + gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE? "3": + gpg_err_code (rc) == GPG_ERR_CERT_REVOKED? "4": + gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED? "5": + gpg_err_code (rc) == GPG_ERR_NO_CRL_KNOWN? "6": + gpg_err_code (rc) == GPG_ERR_CRL_TOO_OLD? "7": + gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH? "8": + gpg_err_code (rc) == GPG_ERR_NO_SECKEY? "9": + "0", + sl->d, NULL); + } + } + for (sl = remusr; sl; sl = sl->next) + { + int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 0, &recplist); + if (rc) + { + log_error (_("can't encrypt to `%s': %s\n"), + sl->d, gpg_strerror (rc)); + gpgsm_status2 (&ctrl, STATUS_INV_RECP, + gpg_err_code (rc) == -1? "1": + gpg_err_code (rc) == GPG_ERR_NO_PUBKEY? "1": + gpg_err_code (rc) == GPG_ERR_AMBIGUOUS_NAME? "2": + gpg_err_code (rc) == GPG_ERR_WRONG_KEY_USAGE? "3": + gpg_err_code (rc) == GPG_ERR_CERT_REVOKED? "4": + gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED? "5": + gpg_err_code (rc) == GPG_ERR_NO_CRL_KNOWN? "6": + gpg_err_code (rc) == GPG_ERR_CRL_TOO_OLD? "7": + gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH? "8": + "0", + sl->d, NULL); + } + } + if (log_get_errorcount(0)) + gpgsm_exit(1); /* must stop for invalid recipients */ + + + + fname = argc? *argv : NULL; + + switch (cmd) + { + case aServer: + if (debug_wait) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + sleep (debug_wait); + log_debug ("... okay\n"); + } + gpgsm_server (); + break; + + case aCallDirmngr: + if (!argc) + wrong_args (_("--call-dirmngr <command> {args}")); + else + if (gpgsm_dirmngr_run_command (&ctrl, *argv, argc-1, argv+1)) + gpgsm_exit (1); + break; + + case aCallProtectTool: + run_protect_tool (argc, argv); + break; + + case aEncr: /* encrypt the given file */ + if (!argc) + gpgsm_encrypt (&ctrl, recplist, 0, stdout); /* from stdin */ + else if (argc == 1) + gpgsm_encrypt (&ctrl, recplist, open_read (*argv), stdout); /* from file */ + else + wrong_args (_("--encrypt [datafile]")); + break; + + case aSign: /* sign the given file */ + /* FIXME: We don't handle --output yet. We should also allow + to concatenate multiple files for signing because that is + what gpg does.*/ + if (!argc) + gpgsm_sign (&ctrl, signerlist, + 0, detached_sig, stdout); /* create from stdin */ + else if (argc == 1) + gpgsm_sign (&ctrl, signerlist, + open_read (*argv), detached_sig, stdout); /* from file */ + else + wrong_args (_("--sign [datafile]")); + break; + + case aSignEncr: /* sign and encrypt the given file */ + log_error ("this command has not yet been implemented\n"); + break; + + case aClearsign: /* make a clearsig */ + log_error ("this command has not yet been implemented\n"); + break; + + case aVerify: + { + FILE *fp = NULL; + + if (argc == 2 && opt.outfile) + log_info ("option --output ignored for a detached signature\n"); + else if (opt.outfile) + fp = open_fwrite (opt.outfile); + + if (!argc) + gpgsm_verify (&ctrl, 0, -1, fp); /* normal signature from stdin */ + else if (argc == 1) + gpgsm_verify (&ctrl, open_read (*argv), -1, fp); /* std signature */ + else if (argc == 2) /* detached signature (sig, detached) */ + gpgsm_verify (&ctrl, open_read (*argv), open_read (argv[1]), NULL); + else + wrong_args (_("--verify [signature [detached_data]]")); + + if (fp && fp != stdout) + fclose (fp); + } + break; + + case aVerifyFiles: + log_error ("this command has not yet been implemented\n"); + break; + + case aDecrypt: + if (!argc) + gpgsm_decrypt (&ctrl, 0, stdout); /* from stdin */ + else if (argc == 1) + gpgsm_decrypt (&ctrl, open_read (*argv), stdout); /* from file */ + else + wrong_args (_("--decrypt [filename]")); + break; + + case aDeleteKey: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_delete (&ctrl, sl); + free_strlist(sl); + break; + + case aListSigs: + ctrl.with_chain = 1; + case aListKeys: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_list_keys (&ctrl, sl, stdout, (0 | (1<<6))); + free_strlist(sl); + break; + + case aListExternalKeys: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_list_keys (&ctrl, sl, stdout, (0 | (1<<7))); + free_strlist(sl); + break; + + case aListSecretKeys: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_list_keys (&ctrl, sl, stdout, (2 | (1<<6))); + free_strlist(sl); + break; + + case aKeygen: /* generate a key */ + log_error ("this function is not yet available from the commandline\n"); + break; + + case aImport: + gpgsm_import_files (&ctrl, argc, argv, open_read); + break; + + case aExport: + for (sl=NULL; argc; argc--, argv++) + add_to_strlist (&sl, *argv); + gpgsm_export (&ctrl, sl, stdout); + free_strlist(sl); + break; + + + case aSendKeys: + case aRecvKeys: + log_error ("this command has not yet been implemented\n"); + break; + + + case aLearnCard: + if (argc) + wrong_args ("--learn-card"); + else + { + int rc = gpgsm_agent_learn (); + if (rc) + log_error ("error learning card: %s\n", gpg_strerror (rc)); + } + break; + + case aPasswd: + if (argc != 1) + wrong_args ("--passwd <key-Id>"); + else + { + int rc; + KsbaCert cert = NULL; + char *grip = NULL; + + rc = gpgsm_find_cert (*argv, &cert); + if (rc) + ; + else if (!(grip = gpgsm_get_keygrip_hexstring (cert))) + rc = gpg_error (GPG_ERR_BUG); + else + rc = gpgsm_agent_passwd (grip); + if (rc) + log_error ("error changing passphrase: %s\n", gpg_strerror (rc)); + xfree (grip); + ksba_cert_release (cert); + } + break; + + default: + log_error ("invalid command (there is no implicit command)\n"); + break; + } + + /* cleanup */ + gpgsm_release_certlist (recplist); + gpgsm_release_certlist (signerlist); + FREE_STRLIST(remusr); + FREE_STRLIST(locusr); + gpgsm_exit(0); + return 8; /*NEVER REACHED*/ +} + +/* Note: This function is used by signal handlers!. */ +static void +emergency_cleanup (void) +{ + gcry_control (GCRYCTL_TERM_SECMEM ); +} + + +void +gpgsm_exit (int rc) +{ + gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); + if (opt.debug & DBG_MEMSTAT_VALUE) + { + gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); + gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); + } + if (opt.debug) + gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); + emergency_cleanup (); + rc = rc? rc : log_get_errorcount(0)? 2 : gpgsm_errors_seen? 1 : 0; + exit (rc); +} + + +void +gpgsm_init_default_ctrl (struct server_control_s *ctrl) +{ + ctrl->include_certs = 1; /* only include the signer's cert */ +} + + + +/* Check whether the filename has the form "-&nnnn", where n is a + non-zero number. Returns this number or -1 if it is not the case. */ +static int +check_special_filename (const char *fname) +{ + if (allow_special_filenames + && fname && *fname == '-' && fname[1] == '&' ) { + int i; + + fname += 2; + for (i=0; isdigit (fname[i]); i++ ) + ; + if ( !fname[i] ) + return atoi (fname); + } + return -1; +} + + + +/* Open the FILENAME for read and return the filedescriptor. Stop + with an error message in case of problems. "-" denotes stdin and + if special filenames are allowed the given fd is opened instead. */ +static int +open_read (const char *filename) +{ + int fd; + + if (filename[0] == '-' && !filename[1]) + return 0; /* stdin */ + fd = check_special_filename (filename); + if (fd != -1) + return fd; + fd = open (filename, O_RDONLY); + if (fd == -1) + { + log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); + gpgsm_exit (2); + } + return fd; +} + +/* Open FILENAME for fwrite and return the stream. Stop with an error + message in case of problems. "-" denotes stdout and if special + filenames are allowed the given fd is opened instead. Caller must + close the returned stream unless it is stdout. */ +static FILE * +open_fwrite (const char *filename) +{ + int fd; + FILE *fp; + + if (filename[0] == '-' && !filename[1]) + return stdout; + + fd = check_special_filename (filename); + if (fd != -1) + { + fp = fdopen (dup (fd), "wb"); + if (!fp) + { + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + gpgsm_exit (2); + } + return fp; + } + fp = fopen (filename, "wb"); + if (!fp) + { + log_error (_("can't open `%s': %s\n"), filename, strerror (errno)); + gpgsm_exit (2); + } + return fp; +} + + +static void +run_protect_tool (int argc, char **argv) +{ + char *pgm = GNUPG_PROTECT_TOOL; + char **av; + int i; + + av = xcalloc (argc+2, sizeof *av); + av[0] = strrchr (pgm, '/'); + if (!av[0]) + av[0] = pgm; + for (i=1; argc; i++, argc--, argv++) + av[i] = *argv; + av[i] = NULL; + execv (pgm, av); + log_error ("error executing `%s': %s\n", pgm, strerror (errno)); + gpgsm_exit (2); +} + diff --git a/sm/gpgsm.h b/sm/gpgsm.h new file mode 100644 index 000000000..f996d578c --- /dev/null +++ b/sm/gpgsm.h @@ -0,0 +1,274 @@ +/* gpgsm.h - Global definitions for GpgSM + * Copyright (C) 2001, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef GPGSM_H +#define GPGSM_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGSM +#include <gpg-error.h> + +#include <ksba.h> +#include "../common/util.h" +#include "../common/errors.h" + +#define OUT_OF_CORE(a) (gpg_error (gpg_err_code_from_errno ((a)))) + +#define MAX_DIGEST_LEN 24 + +/* A large struct name "opt" to keep global flags */ +struct { + unsigned int debug; /* debug flags (DBG_foo_VALUE) */ + int verbose; /* verbosity level */ + int quiet; /* be as quiet as possible */ + int batch; /* run in batch mode, i.e w/o any user interaction */ + int answer_yes; /* assume yes on most questions */ + int answer_no; /* assume no on most questions */ + int dry_run; /* don't change any persistent data */ + + const char *homedir; /* configuration directory name */ + const char *agent_program; + char *display; + char *ttyname; + char *ttytype; + char *lc_ctype; + char *lc_messages; + + const char *dirmngr_program; + char *outfile; /* name of output file */ + + int with_key_data;/* include raw key in the column delimted output */ + + int fingerprint; /* list fingerprints in all key listings */ + + int armor; /* force base64 armoring (see also ctrl.with_base64) */ + int no_armor; /* don't try to figure out whether data is base64 armored*/ + + const char *def_cipher_algoid; /* cipher algorithm to use if + nothing else is specified */ + + int def_digest_algo; /* Ditto for hash algorithm */ + int def_compress_algo; /* Ditto for compress algorithm */ + + char *def_recipient; /* userID of the default recipient */ + int def_recipient_self; /* The default recipient is the default key */ + + char *local_user; /* NULL or argument to -u */ + + int always_trust; /* Trust the given keys even if there is no + valid certification chain */ + int skip_verify; /* do not check signatures on data */ + + int lock_once; /* Keep lock once they are set */ + + int ignore_time_conflict; /* Ignore certain time conflicts */ + + int no_crl_check; /* Don't do a CRL check */ + + char *policy_file; /* full pathname of policy file */ + int no_policy_check; /* ignore certificate policies */ + int no_chain_validation; /* Bypass all cert chain validity tests */ + + int auto_issuer_key_retrieve; /* try to retrieve a missing issuer key. */ +} opt; + + +#define DBG_X509_VALUE 1 /* debug x.509 data reading/writing */ +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ +#define DBG_CACHE_VALUE 64 /* debug the caching */ +#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ +#define DBG_HASHING_VALUE 512 /* debug hashing operations */ +#define DBG_ASSUAN_VALUE 1024 /* debug assuan communication */ + +#define DBG_X509 (opt.debug & DBG_X509_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) +#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) +#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) +#define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE) + +struct server_local_s; + +/* Note that the default values for this are set by + gpgsm_init_default_ctrl() */ +struct server_control_s { + int no_server; /* we are not running under server control */ + int status_fd; /* only for non-server mode */ + struct server_local_s *server_local; + int with_colons; /* use column delimited output format */ + int with_chain; /* include the certifying certs in a listing */ + + int autodetect_encoding; /* try to detect the input encoding */ + int is_pem; /* Is in PEM format */ + int is_base64; /* is in plain base-64 format */ + + int create_base64; /* Create base64 encoded output */ + int create_pem; /* create PEM output */ + const char *pem_name; /* PEM name to use */ + + int include_certs; /* -1 to send all certificates in the chain + along with a signature or the number of + certificates up the chain (0 = none, 1 = only + signer) */ +}; +typedef struct server_control_s *CTRL; + +/* data structure used in base64.c */ +typedef struct base64_context_s *Base64Context; + + +struct certlist_s { + struct certlist_s *next; + KsbaCert cert; +}; +typedef struct certlist_s *CERTLIST; + +/*-- gpgsm.c --*/ +void gpgsm_exit (int rc); +void gpgsm_init_default_ctrl (struct server_control_s *ctrl); + +/*-- server.c --*/ +void gpgsm_server (void); +void gpgsm_status (CTRL ctrl, int no, const char *text); +void gpgsm_status2 (CTRL ctrl, int no, ...); +void gpgsm_status_with_err_code (CTRL ctrl, int no, const char *text, + gpg_err_code_t ec); + +/*-- fingerprint --*/ +char *gpgsm_get_fingerprint (KsbaCert cert, int algo, char *array, int *r_len); +char *gpgsm_get_fingerprint_string (KsbaCert cert, int algo); +char *gpgsm_get_fingerprint_hexstring (KsbaCert cert, int algo); +unsigned long gpgsm_get_short_fingerprint (KsbaCert cert); +char *gpgsm_get_keygrip (KsbaCert cert, char *array); +char *gpgsm_get_keygrip_hexstring (KsbaCert cert); +char *gpgsm_get_certid (KsbaCert cert); + + +/*-- base64.c --*/ +int gpgsm_create_reader (Base64Context *ctx, + CTRL ctrl, FILE *fp, KsbaReader *r_reader); +void gpgsm_destroy_reader (Base64Context ctx); +int gpgsm_create_writer (Base64Context *ctx, + CTRL ctrl, FILE *fp, KsbaWriter *r_writer); +int gpgsm_finish_writer (Base64Context ctx); +void gpgsm_destroy_writer (Base64Context ctx); + + +/*-- certdump.c --*/ +void gpgsm_print_serial (FILE *fp, KsbaConstSexp p); +void gpgsm_print_time (FILE *fp, time_t t); +void gpgsm_print_name (FILE *fp, const char *string); + +void gpgsm_dump_cert (const char *text, KsbaCert cert); +void gpgsm_dump_serial (KsbaConstSexp p); +void gpgsm_dump_time (time_t t); +void gpgsm_dump_string (const char *string); + + + +/*-- certcheck.c --*/ +int gpgsm_check_cert_sig (KsbaCert issuer_cert, KsbaCert cert); +int gpgsm_check_cms_signature (KsbaCert cert, KsbaConstSexp sigval, + gcry_md_hd_t md, int hash_algo); +/* fixme: move create functions to another file */ +int gpgsm_create_cms_signature (KsbaCert cert, gcry_md_hd_t md, int mdalgo, + char **r_sigval); + + +/*-- certchain.c --*/ +int gpgsm_walk_cert_chain (KsbaCert start, KsbaCert *r_next); +int gpgsm_is_root_cert (KsbaCert cert); +int gpgsm_validate_chain (CTRL ctrl, KsbaCert cert, time_t *r_exptime); +int gpgsm_basic_cert_check (KsbaCert cert); + +/*-- certlist.c --*/ +int gpgsm_cert_use_sign_p (KsbaCert cert); +int gpgsm_cert_use_encrypt_p (KsbaCert cert); +int gpgsm_cert_use_verify_p (KsbaCert cert); +int gpgsm_cert_use_decrypt_p (KsbaCert cert); +int gpgsm_cert_use_cert_p (KsbaCert cert); +int gpgsm_add_to_certlist (CTRL ctrl, const char *name, int secret, + CERTLIST *listaddr); +void gpgsm_release_certlist (CERTLIST list); +int gpgsm_find_cert (const char *name, KsbaCert *r_cert); + +/*-- keylist.c --*/ +void gpgsm_list_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode); + +/*-- import.c --*/ +int gpgsm_import (CTRL ctrl, int in_fd); +int gpgsm_import_files (CTRL ctrl, int nfiles, char **files, + int (*of)(const char *fname)); + +/*-- export.c --*/ +void gpgsm_export (CTRL ctrl, STRLIST names, FILE *fp); + +/*-- delete.c --*/ +int gpgsm_delete (CTRL ctrl, STRLIST names); + +/*-- verify.c --*/ +int gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp); + +/*-- sign.c --*/ +int gpgsm_get_default_cert (KsbaCert *r_cert); +int gpgsm_sign (CTRL ctrl, CERTLIST signerlist, + int data_fd, int detached, FILE *out_fp); + +/*-- encrypt.c --*/ +int gpgsm_encrypt (CTRL ctrl, CERTLIST recplist, int in_fd, FILE *out_fp); + +/*-- decrypt.c --*/ +int gpgsm_decrypt (CTRL ctrl, int in_fd, FILE *out_fp); + +/*-- certreqgen.c --*/ +int gpgsm_genkey (CTRL ctrl, int in_fd, FILE *out_fp); + +/*-- call-agent.c --*/ +int gpgsm_agent_pksign (const char *keygrip, + unsigned char *digest, + size_t digestlen, + int digestalgo, + char **r_buf, size_t *r_buflen); +int gpgsm_agent_pkdecrypt (const char *keygrip, + KsbaConstSexp ciphertext, + char **r_buf, size_t *r_buflen); +int gpgsm_agent_genkey (KsbaConstSexp keyparms, KsbaSexp *r_pubkey); +int gpgsm_agent_istrusted (KsbaCert cert); +int gpgsm_agent_havekey (const char *hexkeygrip); +int gpgsm_agent_marktrusted (KsbaCert cert); +int gpgsm_agent_learn (void); +int gpgsm_agent_passwd (const char *hexkeygrip); + +/*-- call-dirmngr.c --*/ +int gpgsm_dirmngr_isvalid (KsbaCert cert); +int gpgsm_dirmngr_lookup (CTRL ctrl, STRLIST names, + void (*cb)(void*, KsbaCert), void *cb_value); +int gpgsm_dirmngr_run_command (CTRL ctrl, const char *command, + int argc, char **argv); + + + + + +#endif /*GPGSM_H*/ diff --git a/sm/import.c b/sm/import.c new file mode 100644 index 000000000..17dc3d66c --- /dev/null +++ b/sm/import.c @@ -0,0 +1,349 @@ +/* import.c - Import certificates + * Copyright (C) 2001, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + +struct stats_s { + unsigned long count; + unsigned long imported; + unsigned long unchanged; + unsigned long not_imported; +}; + + + +static void +print_imported_status (CTRL ctrl, KsbaCert cert) +{ + char *fpr; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + gpgsm_status2 (ctrl, STATUS_IMPORTED, fpr, "[X.509]", NULL); + xfree (fpr); +} + + +/* Print an IMPORT_PROBLEM status. REASON is one of: + 0 := "No specific reason given". + 1 := "Invalid Certificate". + 2 := "Issuer Certificate missing". + 3 := "Certificate Chain too long". + 4 := "Error storing certificate". +*/ +static void +print_import_problem (CTRL ctrl, KsbaCert cert, int reason) +{ + char *fpr = NULL; + char buf[25]; + int i; + + sprintf (buf, "%d", reason); + if (cert) + { + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + /* detetect an error (all high) value */ + for (i=0; fpr[i] == 'F'; i++) + ; + if (!fpr[i]) + { + xfree (fpr); + fpr = NULL; + } + } + gpgsm_status2 (ctrl, STATUS_IMPORT_PROBLEM, buf, fpr, NULL); + xfree (fpr); +} + + +void +print_imported_summary (CTRL ctrl, struct stats_s *stats) +{ + char buf[14*25]; + + if (!opt.quiet) + { + log_info (_("total number processed: %lu\n"), stats->count); + if (stats->imported) + { + log_info (_(" imported: %lu"), stats->imported ); + log_printf ("\n"); + } + if (stats->unchanged) + log_info (_(" unchanged: %lu\n"), stats->unchanged); + if (stats->not_imported) + log_info (_(" not imported: %lu\n"), stats->not_imported); + } + + sprintf (buf, "%lu 0 %lu 0 %lu 0 0 0 0 0 0 0 0 %lu", + stats->count, + stats->imported, + stats->unchanged, + stats->not_imported + ); + gpgsm_status (ctrl, STATUS_IMPORT_RES, buf); +} + + + +static void +check_and_store (CTRL ctrl, struct stats_s *stats, KsbaCert cert, int depth) +{ + int rc; + + stats->count++; + if ( depth >= 50 ) + { + log_error (_("certificate chain too long\n")); + stats->not_imported++; + print_import_problem (ctrl, cert, 3); + return; + } + + rc = gpgsm_basic_cert_check (cert); + if (!rc) + { + int existed; + + if (!keydb_store_cert (cert, 0, &existed)) + { + KsbaCert next = NULL; + + if (!existed) + { + print_imported_status (ctrl, cert); + stats->imported++; + } + else + stats->unchanged++; + + if (opt.verbose > 1 && existed) + { + if (depth) + log_info ("issuer certificate already in DB\n"); + else + log_info ("certificate already in DB\n"); + } + else if (opt.verbose && !existed) + { + if (depth) + log_info ("issuer certificate imported\n"); + else + log_info ("certificate imported\n"); + } + /* Now lets walk up the chain and import all certificates up + the chain.*/ + else if (!gpgsm_walk_cert_chain (cert, &next)) + { + check_and_store (ctrl, stats, next, depth+1); + ksba_cert_release (next); + } + } + else + { + log_error (_("error storing certificate\n")); + stats->not_imported++; + print_import_problem (ctrl, cert, 4); + } + } + else + { + log_error (_("basic certificate checks failed - not imported\n")); + stats->not_imported++; + print_import_problem (ctrl, cert, + gpg_err_code (rc) == GPG_ERR_MISSING_CERT? 2 : + gpg_err_code (rc) == GPG_ERR_BAD_CERT? 1 : 0); + } +} + + + + +static int +import_one (CTRL ctrl, struct stats_s *stats, int in_fd) +{ + int rc; + Base64Context b64reader = NULL; + KsbaReader reader; + KsbaCert cert = NULL; + KsbaCMS cms = NULL; + FILE *fp = NULL; + KsbaContentType ct; + + fp = fdopen ( dup (in_fd), "rb"); + if (!fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gpgsm_create_reader (&b64reader, ctrl, fp, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + ct = ksba_cms_identify (reader); + if (ct == KSBA_CT_SIGNED_DATA) + { /* This is probably a signed-only message - import the certs */ + KsbaStopReason stopreason; + int i; + + cms = ksba_cms_new (); + if (!cms) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + rc = ksba_cms_set_reader_writer (cms, reader, NULL); + if (rc) + { + log_error ("ksba_cms_set_reader_writer failed: %s\n", + ksba_strerror (rc)); + rc = map_ksba_err (rc); + goto leave; + } + + + do + { + rc = ksba_cms_parse (cms, &stopreason); + if (rc) + { + log_error ("ksba_cms_parse failed: %s\n", ksba_strerror (rc)); + rc = map_ksba_err (rc); + goto leave; + } + + if (stopreason == KSBA_SR_BEGIN_DATA) + log_info ("not a certs-only message\n"); + } + while (stopreason != KSBA_SR_READY); + + for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) + { + check_and_store (ctrl, stats, cert, 0); + ksba_cert_release (cert); + cert = NULL; + } + if (!i) + log_error ("no certificate found\n"); + } + else if (ct == KSBA_CT_NONE) + { /* Failed to identify this message - assume a certificate */ + + cert = ksba_cert_new (); + if (!cert) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + rc = ksba_cert_read_der (cert, reader); + if (rc) + { + rc = map_ksba_err (rc); + goto leave; + } + + check_and_store (ctrl, stats, cert, 0); + } + else + { + log_error ("can't extract certificates from input\n"); + rc = gpg_error (GPG_ERR_NO_DATA); + } + + leave: + ksba_cms_release (cms); + ksba_cert_release (cert); + gpgsm_destroy_reader (b64reader); + if (fp) + fclose (fp); + return rc; +} + + +int +gpgsm_import (CTRL ctrl, int in_fd) +{ + int rc; + struct stats_s stats; + + memset (&stats, 0, sizeof stats); + rc = import_one (ctrl, &stats, in_fd); + print_imported_summary (ctrl, &stats); + /* If we never printed an error message do it now so that a command + line invocation will return with an error (log_error keeps a + global errorcount) */ + if (rc && !log_get_errorcount (0)) + log_error (_("error importing certificate: %s\n"), gpg_strerror (rc)); + return rc; +} + + +int +gpgsm_import_files (CTRL ctrl, int nfiles, char **files, + int (*of)(const char *fname)) +{ + int rc = 0; + struct stats_s stats; + + memset (&stats, 0, sizeof stats); + + if (!nfiles) + rc = import_one (ctrl, &stats, 0); + else + { + for (; nfiles && !rc ; nfiles--, files++) + { + int fd = of (*files); + rc = import_one (ctrl, &stats, fd); + close (fd); + if (rc == -1) + rc = 0; + } + } + print_imported_summary (ctrl, &stats); + /* If we never printed an error message do it now so that a command + line invocation will return with an error (log_error keeps a + global errorcount) */ + if (rc && !log_get_errorcount (0)) + log_error (_("error importing certificate: %s\n"), gpg_strerror (rc)); + return rc; +} + + diff --git a/sm/keydb.c b/sm/keydb.c new file mode 100644 index 000000000..fe6556549 --- /dev/null +++ b/sm/keydb.c @@ -0,0 +1,1282 @@ +/* keydb.c - key database dispatcher + * Copyright (C) 2001, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "gpgsm.h" +#include "../kbx/keybox.h" +#include "keydb.h" +#include "i18n.h" + +#define DIRSEP_C '/' + +static int active_handles; + +typedef enum { + KEYDB_RESOURCE_TYPE_NONE = 0, + KEYDB_RESOURCE_TYPE_KEYBOX +} KeydbResourceType; +#define MAX_KEYDB_RESOURCES 20 + +struct resource_item { + KeydbResourceType type; + union { + KEYBOX_HANDLE kr; + } u; + void *token; + int secret; + DOTLOCK lockhandle; +}; + +static struct resource_item all_resources[MAX_KEYDB_RESOURCES]; +static int used_resources; + +struct keydb_handle { + int locked; + int found; + int current; + int is_ephemeral; + int used; /* items in active */ + struct resource_item active[MAX_KEYDB_RESOURCES]; +}; + + +static int lock_all (KEYDB_HANDLE hd); +static void unlock_all (KEYDB_HANDLE hd); + + +/* + * Register a resource (which currently may only be a keybox file). + * The first keybox which is added by this function is + * created if it does not exist. + * Note: this function may be called before secure memory is + * available. + */ +int +keydb_add_resource (const char *url, int force, int secret) +{ + static int any_secret, any_public; + const char *resname = url; + char *filename = NULL; + int rc = 0; + FILE *fp; + KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE; + const char *created_fname = NULL; + + /* Do we have an URL? + gnupg-kbx:filename := this is a plain keybox + filename := See what is is, but create as plain keybox. + */ + if (strlen (resname) > 10) + { + if (!strncmp (resname, "gnupg-kbx:", 10) ) + { + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + resname += 10; + } +#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__) + else if (strchr (resname, ':')) + { + log_error ("invalid key resource URL `%s'\n", url ); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } +#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */ + } + + if (*resname != DIRSEP_C ) + { /* do tilde expansion etc */ + if (strchr(resname, DIRSEP_C) ) + filename = make_filename (resname, NULL); + else + filename = make_filename (opt.homedir, resname, NULL); + } + else + filename = xstrdup (resname); + + if (!force) + force = secret? !any_secret : !any_public; + + /* see whether we can determine the filetype */ + if (rt == KEYDB_RESOURCE_TYPE_NONE) + { + FILE *fp2 = fopen( filename, "rb" ); + + if (fp2) { + u32 magic; + + /* FIXME: check for the keybox magic */ + if (fread( &magic, 4, 1, fp2) == 1 ) + { + if (magic == 0x13579ace || magic == 0xce9a5713) + ; /* GDBM magic - no more support */ + else + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + } + else /* maybe empty: assume ring */ + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + fclose (fp2); + } + else /* no file yet: create ring */ + rt = KEYDB_RESOURCE_TYPE_KEYBOX; + } + + switch (rt) + { + case KEYDB_RESOURCE_TYPE_NONE: + log_error ("unknown type of key resource `%s'\n", url ); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + + case KEYDB_RESOURCE_TYPE_KEYBOX: + fp = fopen (filename, "rb"); + if (!fp && !force) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + goto leave; + } + + if (!fp) + { /* no file */ +#if 0 /* no autocreate of the homedirectory yet */ + { + char *last_slash_in_filename; + + last_slash_in_filename = strrchr (filename, DIRSEP_C); + *last_slash_in_filename = 0; + if (access (filename, F_OK)) + { /* on the first time we try to create the default + homedir and in this case the process will be + terminated, so that on the next invocation can + read the options file in on startup */ + try_make_homedir (filename); + rc = gpg_error (GPG_ERR_FILE_OPEN_ERROR); + *last_slash_in_filename = DIRSEP_C; + goto leave; + } + *last_slash_in_filename = DIRSEP_C; + } +#endif + fp = fopen (filename, "w"); + if (!fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error (_("error creating keybox `%s': %s\n"), + filename, strerror(errno)); + goto leave; + } + + if (!opt.quiet) + log_info (_("keybox `%s' created\n"), filename); + created_fname = filename; + } + fclose (fp); + fp = NULL; + /* now register the file */ + { + + void *token = keybox_register_file (filename, secret); + if (!token) + ; /* already registered - ignore it */ + else if (used_resources >= MAX_KEYDB_RESOURCES) + rc = gpg_error (GPG_ERR_RESOURCE_LIMIT); + else + { + all_resources[used_resources].type = rt; + all_resources[used_resources].u.kr = NULL; /* Not used here */ + all_resources[used_resources].token = token; + all_resources[used_resources].secret = secret; + + all_resources[used_resources].lockhandle + = create_dotlock (filename); + if (!all_resources[used_resources].lockhandle) + log_fatal ( _("can't create lock for `%s'\n"), filename); + + used_resources++; + } + } + break; + default: + log_error ("resource type of `%s' not supported\n", url); + rc = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + /* fixme: check directory permissions and print a warning */ + + leave: + if (rc) + log_error ("keyblock resource `%s': %s\n", filename, gpg_strerror(rc)); + else if (secret) + any_secret = 1; + else + any_public = 1; + xfree (filename); + return rc; +} + + +KEYDB_HANDLE +keydb_new (int secret) +{ + KEYDB_HANDLE hd; + int i, j; + + hd = xcalloc (1, sizeof *hd); + hd->found = -1; + + assert (used_resources <= MAX_KEYDB_RESOURCES); + for (i=j=0; i < used_resources; i++) + { + if (!all_resources[i].secret != !secret) + continue; + switch (all_resources[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + hd->active[j].type = all_resources[i].type; + hd->active[j].token = all_resources[i].token; + hd->active[j].secret = all_resources[i].secret; + hd->active[j].lockhandle = all_resources[i].lockhandle; + hd->active[j].u.kr = keybox_new (all_resources[i].token, secret); + if (!hd->active[j].u.kr) + { + xfree (hd); + return NULL; /* fixme: release all previously allocated handles*/ + } + j++; + break; + } + } + hd->used = j; + + active_handles++; + return hd; +} + +void +keydb_release (KEYDB_HANDLE hd) +{ + int i; + + if (!hd) + return; + assert (active_handles > 0); + active_handles--; + + unlock_all (hd); + for (i=0; i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_release (hd->active[i].u.kr); + break; + } + } + + xfree (hd); +} + + +/* Return the name of the current resource. This is function first + looks for the last found found, then for the current search + position, and last returns the first available resource. The + returned string is only valid as long as the handle exists. This + function does only return NULL if no handle is specified, in all + other error cases an empty string is returned. */ +const char * +keydb_get_resource_name (KEYDB_HANDLE hd) +{ + int idx; + const char *s = NULL; + + if (!hd) + return NULL; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + idx = 0; + + switch (hd->active[idx].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + s = NULL; + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + s = keybox_get_resource_name (hd->active[idx].u.kr); + break; + } + + return s? s: ""; +} + +/* Switch the handle into ephemeral mode and return the orginal value. */ +int +keydb_set_ephemeral (KEYDB_HANDLE hd, int yes) +{ + int i; + + if (!hd) + return 0; + + yes = !!yes; + if (hd->is_ephemeral != yes) + { + for (i=0; i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + keybox_set_ephemeral (hd->active[i].u.kr, yes); + break; + } + } + } + + i = hd->is_ephemeral; + hd->is_ephemeral = yes; + return i; +} + + + +static int +lock_all (KEYDB_HANDLE hd) +{ + int i, rc = 0; + + /* Fixme: This locking scheme may lead to deadlock if the resources + are not added in the same sequence by all processes. We are + cuurently only allowing one resource so it is not a problem. */ + for (i=0; i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (hd->active[i].lockhandle) + rc = make_dotlock (hd->active[i].lockhandle, -1); + break; + } + if (rc) + break; + } + + if (rc) + { + /* revert the already set locks */ + for (i--; i >= 0; i--) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (hd->active[i].lockhandle) + release_dotlock (hd->active[i].lockhandle); + break; + } + } + } + else + hd->locked = 1; + + return rc; +} + +static void +unlock_all (KEYDB_HANDLE hd) +{ + int i; + + if (!hd->locked) + return; + + for (i=hd->used-1; i >= 0; i--) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (hd->active[i].lockhandle) + release_dotlock (hd->active[i].lockhandle); + break; + } + } + hd->locked = 0; +} + + +#if 0 +/* + * Return the last found keybox. Caller must free it. + * The returned keyblock has the kbode flag bit 0 set for the node with + * the public key used to locate the keyblock or flag bit 1 set for + * the user ID node. + */ +int +keydb_get_keyblock (KEYDB_HANDLE hd, KBNODE *ret_kb) +{ + int rc = 0; + + if (!hd) + return G10ERR_INV_ARG; + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + switch (hd->active[hd->found].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_get_keyblock (hd->active[hd->found].u.kr, ret_kb); + break; + } + + return rc; +} + +/* + * update the current keyblock with KB + */ +int +keydb_update_keyblock (KEYDB_HANDLE hd, KBNODE kb) +{ + int rc = 0; + + if (!hd) + return G10ERR_INV_ARG; + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if( opt.dry_run ) + return 0; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[hd->found].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_update_keyblock (hd->active[hd->found].u.kr, kb); + break; + } + + unlock_all (hd); + return rc; +} + + +/* + * Insert a new KB into one of the resources. + */ +int +keydb_insert_keyblock (KEYDB_HANDLE hd, KBNODE kb) +{ + int rc = -1; + int idx; + + if (!hd) + return G10ERR_INV_ARG; + + if( opt.dry_run ) + return 0; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + return G10ERR_GENERAL; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[idx].type) { + case KEYDB_RESOURCE_TYPE_NONE: + rc = G10ERR_GENERAL; /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_insert_keyblock (hd->active[idx].u.kr, kb); + break; + } + + unlock_all (hd); + return rc; +} + +#endif /*disabled code*/ + + + +/* + Return the last found keybox. Caller must free it. The returned + keyblock has the kbode flag bit 0 set for the node with the public + key used to locate the keyblock or flag bit 1 set for the user ID + node. */ +int +keydb_get_cert (KEYDB_HANDLE hd, KsbaCert *r_cert) +{ + int rc = 0; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_get_cert (hd->active[hd->found].u.kr, r_cert); + break; + } + + return rc; +} + +/* + * Insert a new Certificate into one of the resources. + */ +int +keydb_insert_cert (KEYDB_HANDLE hd, KsbaCert cert) +{ + int rc = -1; + int idx; + char digest[20]; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if (opt.dry_run) + return 0; + + if ( hd->found >= 0 && hd->found < hd->used) + idx = hd->found; + else if ( hd->current >= 0 && hd->current < hd->used) + idx = hd->current; + else + return gpg_error (GPG_ERR_GENERAL); + + rc = lock_all (hd); + if (rc) + return rc; + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ + + switch (hd->active[idx].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_insert_cert (hd->active[idx].u.kr, cert, digest); + break; + } + + unlock_all (hd); + return rc; +} + + + +/* update the current keyblock with KB */ +int +keydb_update_cert (KEYDB_HANDLE hd, KsbaCert cert) +{ + int rc = 0; + char digest[20]; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if (opt.dry_run) + return 0; + + rc = lock_all (hd); + if (rc) + return rc; + + gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/ + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); /* oops */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest); + break; + } + + unlock_all (hd); + return rc; +} + + +/* + * The current keyblock or cert will be deleted. + */ +int +keydb_delete (KEYDB_HANDLE hd) +{ + int rc = -1; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if ( hd->found < 0 || hd->found >= hd->used) + return -1; /* nothing found */ + + if( opt.dry_run ) + return 0; + + rc = lock_all (hd); + if (rc) + return rc; + + switch (hd->active[hd->found].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + rc = gpg_error (GPG_ERR_GENERAL); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_delete (hd->active[hd->found].u.kr); + break; + } + + unlock_all (hd); + return rc; +} + + + +/* + * Locate the default writable key resource, so that the next + * operation (which is only relevant for inserts) will be done on this + * resource. + */ +int +keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved) +{ + int rc; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + rc = keydb_search_reset (hd); /* this does reset hd->current */ + if (rc) + return rc; + + for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++) + { + switch (hd->active[hd->current].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + BUG(); + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + if (keybox_is_writable (hd->active[hd->current].token)) + return 0; /* found (hd->current is set to it) */ + break; + } + } + + return -1; +} + +/* + * Rebuild the caches of all key resources. + */ +void +keydb_rebuild_caches (void) +{ + int i; + + for (i=0; i < used_resources; i++) + { + if (all_resources[i].secret) + continue; + switch (all_resources[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: /* ignore */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: +/* rc = keybox_rebuild_cache (all_resources[i].token); */ +/* if (rc) */ +/* log_error (_("failed to rebuild keybox cache: %s\n"), */ +/* g10_errstr (rc)); */ + break; + } + } +} + + + +/* + * Start the next search on this handle right at the beginning + */ +int +keydb_search_reset (KEYDB_HANDLE hd) +{ + int i, rc = 0; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + hd->current = 0; + hd->found = -1; + /* and reset all resources */ + for (i=0; !rc && i < hd->used; i++) + { + switch (hd->active[i].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_search_reset (hd->active[i].u.kr); + break; + } + } + return rc; /* fixme: we need to map error codes or share them with + all modules*/ +} + +/* + * Search through all keydb resources, starting at the current position, + * for a keyblock which contains one of the keys described in the DESC array. + */ +int +keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc, size_t ndesc) +{ + int rc = -1; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + while (rc == -1 && hd->current >= 0 && hd->current < hd->used) + { + switch (hd->active[hd->current].type) + { + case KEYDB_RESOURCE_TYPE_NONE: + BUG(); /* we should never see it here */ + break; + case KEYDB_RESOURCE_TYPE_KEYBOX: + rc = keybox_search (hd->active[hd->current].u.kr, desc, ndesc); + break; + } + if (rc == -1) /* EOF -> switch to next resource */ + hd->current++; + else if (!rc) + hd->found = hd->current; + } + + return rc; +} + + +int +keydb_search_first (KEYDB_HANDLE hd) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FIRST; + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_next (KEYDB_HANDLE hd) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_NEXT; + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_kid (KEYDB_HANDLE hd, u32 *kid) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_LONG_KID; +/* desc.u.kid[0] = kid[0]; */ +/* desc.u.kid[1] = kid[1]; */ + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_fpr (KEYDB_HANDLE hd, const byte *fpr) +{ + KEYDB_SEARCH_DESC desc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_FPR; + memcpy (desc.u.fpr, fpr, 20); + return keydb_search (hd, &desc, 1); +} + +int +keydb_search_issuer (KEYDB_HANDLE hd, const char *issuer) +{ + KEYDB_SEARCH_DESC desc; + int rc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_ISSUER; + desc.u.name = issuer; + rc = keydb_search (hd, &desc, 1); + return rc; +} + +int +keydb_search_issuer_sn (KEYDB_HANDLE hd, + const char *issuer, KsbaConstSexp serial) +{ + KEYDB_SEARCH_DESC desc; + int rc; + const unsigned char *s; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_ISSUER_SN; + s = serial; + if (*s !='(') + return gpg_error (GPG_ERR_INV_VALUE); + s++; + for (desc.snlen = 0; digitp (s); s++) + desc.snlen = 10*desc.snlen + atoi_1 (s); + if (*s !=':') + return gpg_error (GPG_ERR_INV_VALUE); + desc.sn = s+1; + desc.u.name = issuer; + rc = keydb_search (hd, &desc, 1); + return rc; +} + +int +keydb_search_subject (KEYDB_HANDLE hd, const char *name) +{ + KEYDB_SEARCH_DESC desc; + int rc; + + memset (&desc, 0, sizeof desc); + desc.mode = KEYDB_SEARCH_MODE_SUBJECT; + desc.u.name = name; + rc = keydb_search (hd, &desc, 1); + return rc; +} + + +static int +hextobyte (const unsigned char *s) +{ + int c; + + if( *s >= '0' && *s <= '9' ) + c = 16 * (*s - '0'); + else if ( *s >= 'A' && *s <= 'F' ) + c = 16 * (10 + *s - 'A'); + else if ( *s >= 'a' && *s <= 'f' ) + c = 16 * (10 + *s - 'a'); + else + return -1; + s++; + if ( *s >= '0' && *s <= '9' ) + c += *s - '0'; + else if ( *s >= 'A' && *s <= 'F' ) + c += 10 + *s - 'A'; + else if ( *s >= 'a' && *s <= 'f' ) + c += 10 + *s - 'a'; + else + return -1; + return c; +} + + +static int +classify_user_id (const char *name, + KEYDB_SEARCH_DESC *desc, + int *force_exact ) +{ + const char *s; + int hexprefix = 0; + int hexlength; + int mode = 0; + + /* clear the structure so that the mode field is set to zero unless + * we set it to the correct value right at the end of this function */ + memset (desc, 0, sizeof *desc); + *force_exact = 0; + /* skip leading spaces. Fixme: what about trailing white space? */ + for(s = name; *s && spacep (s); s++ ) + ; + + switch (*s) + { + case 0: /* empty string is an error */ + return 0; + + case '.': /* an email address, compare from end */ + mode = KEYDB_SEARCH_MODE_MAILEND; + s++; + desc->u.name = s; + break; + + case '<': /* an email address */ + mode = KEYDB_SEARCH_MODE_MAIL; + s++; + desc->u.name = s; + break; + + case '@': /* part of an email address */ + mode = KEYDB_SEARCH_MODE_MAILSUB; + s++; + desc->u.name = s; + break; + + case '=': /* exact compare */ + mode = KEYDB_SEARCH_MODE_EXACT; + s++; + desc->u.name = s; + break; + + case '*': /* case insensitive substring search */ + mode = KEYDB_SEARCH_MODE_SUBSTR; + s++; + desc->u.name = s; + break; + + case '+': /* compare individual words */ + mode = KEYDB_SEARCH_MODE_WORDS; + s++; + desc->u.name = s; + break; + + case '/': /* subject's DN */ + s++; + if (!*s || spacep (s)) + return 0; /* no DN or prefixed with a space */ + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_SUBJECT; + break; + + case '#': + { + const char *si; + + s++; + if ( *s == '/') + { /* "#/" indicates an issuer's DN */ + s++; + if (!*s || spacep (s)) + return 0; /* no DN or prefixed with a space */ + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_ISSUER; + } + else + { /* serialnumber + optional issuer ID */ + for (si=s; *si && *si != '/'; si++) + { + if (!strchr("01234567890abcdefABCDEF", *si)) + return 0; /* invalid digit in serial number*/ + } + desc->sn = s; + desc->snlen = -1; + if (!*si) + mode = KEYDB_SEARCH_MODE_SN; + else + { + s = si+1; + if (!*s || spacep (s)) + return 0; /* no DN or prefixed with a space */ + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_ISSUER_SN; + } + } + } + break; + + case ':': /*Unified fingerprint */ + { + const char *se, *si; + int i; + + se = strchr (++s,':'); + if (!se) + return 0; + for (i=0,si=s; si < se; si++, i++ ) + { + if (!strchr("01234567890abcdefABCDEF", *si)) + return 0; /* invalid digit */ + } + if (i != 32 && i != 40) + return 0; /* invalid length of fpr*/ + for (i=0,si=s; si < se; i++, si +=2) + desc->u.fpr[i] = hextobyte(si); + for (; i < 20; i++) + desc->u.fpr[i]= 0; + s = se + 1; + mode = KEYDB_SEARCH_MODE_FPR; + } + break; + + default: + if (s[0] == '0' && s[1] == 'x') + { + hexprefix = 1; + s += 2; + } + + hexlength = strspn(s, "0123456789abcdefABCDEF"); + if (hexlength >= 8 && s[hexlength] =='!') + { + *force_exact = 1; + hexlength++; /* just for the following check */ + } + + /* check if a hexadecimal number is terminated by EOS or blank */ + if (hexlength && s[hexlength] && !spacep (s+hexlength)) + { + if (hexprefix) /* a "0x" prefix without correct */ + return 0; /* termination is an error */ + /* The first chars looked like a hex number, but really is + not */ + hexlength = 0; + } + + if (*force_exact) + hexlength--; /* remove the bang */ + + if (hexlength == 8 + || (!hexprefix && hexlength == 9 && *s == '0')) + { /* short keyid */ + unsigned long kid; + if (hexlength == 9) + s++; + kid = strtoul( s, NULL, 16 ); + desc->u.kid[4] = kid >> 24; + desc->u.kid[5] = kid >> 16; + desc->u.kid[6] = kid >> 8; + desc->u.kid[7] = kid; + mode = KEYDB_SEARCH_MODE_SHORT_KID; + } + else if (hexlength == 16 + || (!hexprefix && hexlength == 17 && *s == '0')) + { /* complete keyid */ + unsigned long kid0, kid1; + char buf[9]; + if (hexlength == 17) + s++; + mem2str(buf, s, 9 ); + kid0 = strtoul (buf, NULL, 16); + kid1 = strtoul (s+8, NULL, 16); + desc->u.kid[0] = kid0 >> 24; + desc->u.kid[1] = kid0 >> 16; + desc->u.kid[2] = kid0 >> 8; + desc->u.kid[3] = kid0; + desc->u.kid[4] = kid1 >> 24; + desc->u.kid[5] = kid1 >> 16; + desc->u.kid[6] = kid1 >> 8; + desc->u.kid[7] = kid1; + mode = KEYDB_SEARCH_MODE_LONG_KID; + } + else if (hexlength == 32 + || (!hexprefix && hexlength == 33 && *s == '0')) + { /* md5 fingerprint */ + int i; + if (hexlength == 33) + s++; + memset(desc->u.fpr+16, 0, 4); + for (i=0; i < 16; i++, s+=2) + { + int c = hextobyte(s); + if (c == -1) + return 0; + desc->u.fpr[i] = c; + } + mode = KEYDB_SEARCH_MODE_FPR16; + } + else if (hexlength == 40 + || (!hexprefix && hexlength == 41 && *s == '0')) + { /* sha1/rmd160 fingerprint */ + int i; + if (hexlength == 41) + s++; + for (i=0; i < 20; i++, s+=2) + { + int c = hextobyte(s); + if (c == -1) + return 0; + desc->u.fpr[i] = c; + } + mode = KEYDB_SEARCH_MODE_FPR20; + } + else if (!hexprefix) + { + /* The fingerprint in an X.509 listing is often delimited by + colons, so we try to single this case out. */ + mode = 0; + hexlength = strspn (s, ":0123456789abcdefABCDEF"); + if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength))) + { + int i; + + for (i=0; i < 20; i++, s += 3) + { + int c = hextobyte(s); + if (c == -1 || (i < 19 && s[2] != ':')) + break; + desc->u.fpr[i] = c; + } + if (i == 20) + mode = KEYDB_SEARCH_MODE_FPR20; + } + if (!mode) /* default is substring search */ + { + *force_exact = 0; + desc->u.name = s; + mode = KEYDB_SEARCH_MODE_SUBSTR; + } + } + else + { /* hex number with a prefix but a wrong length */ + return 0; + } + } + + desc->mode = mode; + return mode; +} + + +int +keydb_classify_name (const char *name, KEYDB_SEARCH_DESC *desc) +{ + int dummy; + KEYDB_SEARCH_DESC dummy_desc; + + if (!desc) + desc = &dummy_desc; + + if (!classify_user_id (name, desc, &dummy)) + return gpg_error (GPG_ERR_INV_NAME); + return 0; +} + + +/* Store the certificate in the key DB but make sure that it does not + already exists. We do this simply by comparing the fingerprint. + If EXISTED is not NULL it will be set to true if the certificate + was already in the DB. */ +int +keydb_store_cert (KsbaCert cert, int ephemeral, int *existed) +{ + KEYDB_HANDLE kh; + int rc; + unsigned char fpr[20]; + + if (existed) + *existed = 0; + + if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL)) + { + log_error (_("failed to get the fingerprint\n")); + return gpg_error (GPG_ERR_GENERAL); + } + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + return gpg_error (GPG_ERR_ENOMEM);; + } + + if (ephemeral) + keydb_set_ephemeral (kh, 1); + + rc = keydb_search_fpr (kh, fpr); + if (rc != -1) + { + keydb_release (kh); + if (!rc) + { + if (existed) + *existed = 1; + return 0; /* okay */ + } + log_error (_("problem looking for existing certificate: %s\n"), + gpg_strerror (rc)); + return rc; + } + + rc = keydb_locate_writable (kh, 0); + if (rc) + { + log_error (_("error finding writable keyDB: %s\n"), gpg_strerror (rc)); + keydb_release (kh); + return rc; + } + + rc = keydb_insert_cert (kh, cert); + if (rc) + { + log_error (_("error storing certificate: %s\n"), gpg_strerror (rc)); + keydb_release (kh); + return rc; + } + keydb_release (kh); + return 0; +} + + + diff --git a/sm/keylist.c b/sm/keylist.c new file mode 100644 index 000000000..634bda292 --- /dev/null +++ b/sm/keylist.c @@ -0,0 +1,617 @@ +/* keylist.c + * Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" + +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + +struct list_external_parm_s { + FILE *fp; + int print_header; + int with_colons; + int with_chain; +}; + + + +static void +print_key_data (KsbaCert cert, FILE *fp) +{ +#if 0 + int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0; + int i; + + for(i=0; i < n; i++ ) + { + fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) ); + mpi_print(stdout, pk->pkey[i], 1 ); + putchar(':'); + putchar('\n'); + } +#endif +} + +static void +print_capabilities (KsbaCert cert, FILE *fp) +{ + KsbaError err; + unsigned int use; + + err = ksba_cert_get_key_usage (cert, &use); + if (err == KSBA_No_Data) + { + putc ('e', fp); + putc ('s', fp); + putc ('c', fp); + putc ('E', fp); + putc ('S', fp); + putc ('C', fp); + return; + } + if (err) + { + log_error (_("error getting key usage information: %s\n"), + ksba_strerror (err)); + return; + } + + if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) + putc ('e', fp); + if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) + putc ('s', fp); + if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + putc ('c', fp); + if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) + putc ('E', fp); + if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION))) + putc ('S', fp); + if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + putc ('C', fp); +} + + +static void +print_time (time_t t, FILE *fp) +{ + if (!t) + ; + else if ( t == (time_t)(-1) ) + putc ('?', fp); + else + fprintf (fp, "%lu", (unsigned long)t); +} + + +/* return an allocated string with the email address extracted from a + DN */ +static char * +email_kludge (const char *name) +{ + const unsigned char *p; + unsigned char *buf; + int n; + + if (strncmp (name, "1.2.840.113549.1.9.1=#", 22)) + return NULL; + /* This looks pretty much like an email address in the subject's DN + we use this to add an additional user ID entry. This way, + openSSL generated keys get a nicer and usable listing */ + name += 22; + for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++) + ; + if (*p != '#' || !n) + return NULL; + buf = xtrymalloc (n+3); + if (!buf) + return NULL; /* oops, out of core */ + *buf = '<'; + for (n=1, p=name; *p != '#'; p +=2, n++) + buf[n] = xtoi_2 (p); + buf[n++] = '>'; + buf[n] = 0; + return buf; +} + + + + +/* List one certificate in colon mode */ +static void +list_cert_colon (KsbaCert cert, FILE *fp, int have_secret) +{ + int idx, trustletter = 0; + char *p; + KsbaSexp sexp; + char *fpr; + + fputs (have_secret? "crs:":"crt:", fp); + trustletter = 0; +#if 0 + if (is_not_valid (cert)) + putc ('i', fp); + else if ( is_revoked (cert) ) + putc ('r', fp); + else if ( has_expired (cert)) + putcr ('e', fp); + else +#endif + { + trustletter = '?'; /*get_validity_info ( pk, NULL );*/ + putc (trustletter, fp); + } + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + fprintf (fp, ":%u:%d:%s:", + /*keylen_of_cert (cert)*/1024, + /* pubkey_algo_of_cert (cert)*/1, + fpr+24); + + /* we assume --fixed-list-mode for gpgsm */ + print_time ( ksba_cert_get_validity (cert, 0), fp); + putc (':', fp); + print_time ( ksba_cert_get_validity (cert, 1), fp); + putc (':', fp); + /* field 8, serial number: */ + if ((sexp = ksba_cert_get_serial (cert))) + { + int len; + const unsigned char *s = sexp; + + if (*s == '(') + { + s++; + for (len=0; *s && *s != ':' && digitp (s); s++) + len = len*10 + atoi_1 (s); + if (*s == ':') + for (s++; len; len--, s++) + fprintf (fp,"%02X", *s); + } + xfree (sexp); + } + putc (':', fp); + /* field 9, ownertrust - not used here */ + putc (':', fp); + /* field 10, old user ID - we use it here for the issuer DN */ + if ((p = ksba_cert_get_issuer (cert,0))) + { + print_sanitized_string (fp, p, ':'); + xfree (p); + } + putc (':', fp); + /* field 11, signature class - not used */ + putc (':', fp); + /* field 12, capabilities: */ + print_capabilities (cert, fp); + putc (':', fp); + putc ('\n', fp); + + /* FPR record */ + fprintf (fp, "fpr:::::::::%s:::", fpr); + xfree (fpr); fpr = NULL; + /* print chaining ID (field 13)*/ + { + KsbaCert next; + + if (!gpgsm_walk_cert_chain (cert, &next)) + { + p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1); + fputs (p, fp); + xfree (p); + ksba_cert_release (next); + } + } + putc (':', fp); + putc ('\n', fp); + + + if (opt.with_key_data) + { + if ( (p = gpgsm_get_keygrip_hexstring (cert))) + { + fprintf (fp, "grp:::::::::%s:\n", p); + xfree (p); + } + print_key_data (cert, fp); + } + + for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++) + { + fprintf (fp, "uid:%c::::::::", trustletter); + print_sanitized_string (fp, p, ':'); + putc (':', fp); + putc (':', fp); + putc ('\n', fp); + if (!idx) + { + /* It would be better to get the faked email address from + the keydb. But as long as we don't have a way to pass + the meta data back, we just check it the same way as the + code used to create the keybox meta data does */ + char *pp = email_kludge (p); + if (pp) + { + fprintf (fp, "uid:%c::::::::", trustletter); + print_sanitized_string (fp, pp, ':'); + putc (':', fp); + putc (':', fp); + putc ('\n', fp); + xfree (pp); + } + } + xfree (p); + } +} + + +/* List one certificate in standard mode */ +static void +list_cert_std (KsbaCert cert, FILE *fp, int have_secret) +{ + KsbaError kerr; + KsbaSexp sexp; + char *dn; + time_t t; + int idx; + int is_ca, chainlen; + unsigned int kusage; + char *string, *p; + + sexp = ksba_cert_get_serial (cert); + fputs ("Serial number: ", fp); + gpgsm_print_serial (fp, sexp); + ksba_free (sexp); + putc ('\n', fp); + + dn = ksba_cert_get_issuer (cert, 0); + fputs (" Issuer: ", fp); + gpgsm_print_name (fp, dn); + ksba_free (dn); + putc ('\n', fp); + for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++) + { + fputs (" aka: ", fp); + gpgsm_print_name (fp, dn); + ksba_free (dn); + putc ('\n', fp); + } + + dn = ksba_cert_get_subject (cert, 0); + fputs (" Subject: ", fp); + gpgsm_print_name (fp, dn); + ksba_free (dn); + putc ('\n', fp); + for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++) + { + fputs (" aka: ", fp); + gpgsm_print_name (fp, dn); + ksba_free (dn); + putc ('\n', fp); + } + + t = ksba_cert_get_validity (cert, 0); + fputs (" validity: ", fp); + gpgsm_print_time (fp, t); + fputs (" through ", fp); + t = ksba_cert_get_validity (cert, 1); + gpgsm_print_time (fp, t); + putc ('\n', fp); + + kerr = ksba_cert_get_key_usage (cert, &kusage); + if (kerr != KSBA_No_Data) + { + fputs (" key usage:", fp); + if (kerr) + fprintf (fp, " [error: %s]", ksba_strerror (kerr)); + else + { + if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE)) + fputs (" digitalSignature", fp); + if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION)) + fputs (" nonRepudiation", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT)) + fputs (" keyEncipherment", fp); + if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT)) + fputs (" dataEncipherment", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT)) + fputs (" keyAgreement", fp); + if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN)) + fputs (" certSign", fp); + if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN)) + fputs (" crlSign", fp); + if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY)) + fputs (" encipherOnly", fp); + if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY)) + fputs (" decipherOnly", fp); + } + putc ('\n', fp); + } + + kerr = ksba_cert_get_cert_policies (cert, &string); + if (kerr != KSBA_No_Data) + { + fputs (" policies: ", fp); + if (kerr) + fprintf (fp, "[error: %s]", ksba_strerror (kerr)); + else + { + for (p=string; *p; p++) + { + if (*p == '\n') + *p = ','; + } + print_sanitized_string (fp, string, 0); + xfree (string); + } + putc ('\n', fp); + } + + kerr = ksba_cert_is_ca (cert, &is_ca, &chainlen); + if (kerr || is_ca) + { + fputs (" chain length: ", fp); + if (kerr) + fprintf (fp, "[error: %s]", ksba_strerror (kerr)); + else if (chainlen == -1) + fputs ("unlimited", fp); + else + fprintf (fp, "%d", chainlen); + putc ('\n', fp); + } + + + dn = gpgsm_get_fingerprint_string (cert, 0); + fprintf (fp, " fingerprint: %s\n", dn?dn:"error"); + xfree (dn); +} + +/* Same as standard mode mode list all certifying certts too */ +static void +list_cert_chain (KsbaCert cert, FILE *fp) +{ + KsbaCert next = NULL; + + list_cert_std (cert, fp, 0); + ksba_cert_ref (cert); + while (!gpgsm_walk_cert_chain (cert, &next)) + { + ksba_cert_release (cert); + fputs ("Certified by\n", fp); + list_cert_std (next, fp, 0); + cert = next; + } + ksba_cert_release (cert); + putc ('\n', fp); +} + + + +/* List all internal keys or just the key given as NAMES. + */ +static void +list_internal_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode) +{ + KEYDB_HANDLE hd; + KEYDB_SEARCH_DESC *desc = NULL; + STRLIST sl; + int ndesc; + KsbaCert cert = NULL; + int rc=0; + const char *lastresname, *resname; + int have_secret; + + hd = keydb_new (0); + if (!hd) + { + log_error ("keydb_new failed\n"); + goto leave; + } + + if (!names) + ndesc = 1; + else + { + for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++) + ; + } + + desc = xtrycalloc (ndesc, sizeof *desc); + if (!ndesc) + { + log_error ("out of core\n"); + goto leave; + } + + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_FIRST; + else + { + for (ndesc=0, sl=names; sl; sl = sl->next) + { + rc = keydb_classify_name (sl->d, desc+ndesc); + if (rc) + { + log_error ("key `%s' not found: %s\n", + sl->d, gpg_strerror (rc)); + rc = 0; + } + else + ndesc++; + } + + } + + /* it would be nice to see which of the given users did actually + match one in the keyring. To implement this we need to have a + found flag for each entry in desc and to set this we must check + all those entries after a match to mark all matched one - + currently we stop at the first match. To do this we need an + extra flag to enable this feature so */ + + lastresname = NULL; + while (!(rc = keydb_search (hd, desc, ndesc))) + { + if (!names) + desc[0].mode = KEYDB_SEARCH_MODE_NEXT; + + rc = keydb_get_cert (hd, &cert); + if (rc) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + resname = keydb_get_resource_name (hd); + + if (lastresname != resname ) + { + int i; + + if (ctrl->no_server) + { + fprintf (fp, "%s\n", resname ); + for (i=strlen(resname); i; i-- ) + putchar('-'); + putc ('\n', fp); + lastresname = resname; + } + } + + have_secret = 0; + if (mode) + { + char *p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + if (!gpgsm_agent_havekey (p)) + have_secret = 1; + xfree (p); + } + } + + if (!mode + || ((mode & 1) && !have_secret) + || ((mode & 2) && have_secret) ) + { + if (ctrl->with_colons) + list_cert_colon (cert, fp, have_secret); + else if (ctrl->with_chain) + list_cert_chain (cert, fp); + else + { + list_cert_std (cert, fp, have_secret); + putc ('\n', fp); + } + } + ksba_cert_release (cert); + cert = NULL; + } + if (rc && rc != -1) + log_error ("keydb_search failed: %s\n", gpg_strerror (rc)); + + leave: + ksba_cert_release (cert); + xfree (desc); + keydb_release (hd); +} + + + +static void +list_external_cb (void *cb_value, KsbaCert cert) +{ + struct list_external_parm_s *parm = cb_value; + + if (keydb_store_cert (cert, 1, NULL)) + log_error ("error storing certificate as ephemeral\n"); + + if (parm->print_header) + { + const char *resname = "[external keys]"; + int i; + + fprintf (parm->fp, "%s\n", resname ); + for (i=strlen(resname); i; i-- ) + putchar('-'); + putc ('\n', parm->fp); + parm->print_header = 0; + } + + if (parm->with_colons) + list_cert_colon (cert, parm->fp, 0); + else if (parm->with_chain) + list_cert_chain (cert, parm->fp); + else + { + list_cert_std (cert, parm->fp, 0); + putc ('\n', parm->fp); + } +} + + +/* List external keys similar to internal one. Note: mode does not + make sense here because it would be unwise to list external secret + keys */ +static void +list_external_keys (CTRL ctrl, STRLIST names, FILE *fp) +{ + int rc; + struct list_external_parm_s parm; + + parm.fp = fp; + parm.print_header = ctrl->no_server; + parm.with_colons = ctrl->with_colons; + parm.with_chain = ctrl->with_chain; + + rc = gpgsm_dirmngr_lookup (ctrl, names, list_external_cb, &parm); + if (rc) + log_error ("listing external keys failed: %s\n", gpg_strerror (rc)); +} + +/* List all keys or just the key given as NAMES. + MODE controls the operation mode: + Bit 0-2: + 0 = list all public keys but don't flag secret ones + 1 = list only public keys + 2 = list only secret keys + 3 = list secret and public keys + Bit 6: list internal keys + Bit 7: list external keys + */ +void +gpgsm_list_keys (CTRL ctrl, STRLIST names, FILE *fp, unsigned int mode) +{ + if ((mode & (1<<6))) + list_internal_keys (ctrl, names, fp, (mode & 3)); + if ((mode & (1<<7))) + list_external_keys (ctrl, names, fp); +} diff --git a/sm/server.c b/sm/server.c new file mode 100644 index 000000000..dda150964 --- /dev/null +++ b/sm/server.c @@ -0,0 +1,1070 @@ +/* server.c - Server mode and main entry point + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <ctype.h> +#include <unistd.h> + +#include <assuan.h> + +#include "gpgsm.h" + +#define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t)) + + +/* The filepointer for status message used in non-server mode */ +static FILE *statusfp; + +/* Data used to assuciate an Assuan context with local server data */ +struct server_local_s { + ASSUAN_CONTEXT assuan_ctx; + int message_fd; + int list_internal; + int list_external; + CERTLIST recplist; + CERTLIST signerlist; +}; + + + +/* note, that it is sufficient to allocate the target string D as + long as the source string S, i.e.: strlen(s)+1; */ +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; +} + + + + +/* Check whether the option NAME appears in LINE */ +static int +has_option (const char *line, const char *name) +{ + const char *s; + int n = strlen (name); + + s = strstr (line, name); + return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); +} + + +static void +close_message_fd (CTRL ctrl) +{ + if (ctrl->server_local->message_fd != -1) + { + close (ctrl->server_local->message_fd); + ctrl->server_local->message_fd = -1; + } +} + + +static int +option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + if (!strcmp (key, "include-certs")) + { + int i = *value? atoi (value) : -1; + if (ctrl->include_certs < -2) + return ASSUAN_Parameter_Error; + ctrl->include_certs = i; + } + else if (!strcmp (key, "display")) + { + if (opt.display) + free (opt.display); + opt.display = strdup (value); + if (!opt.display) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "ttyname")) + { + if (opt.ttyname) + free (opt.ttyname); + opt.ttyname = strdup (value); + if (!opt.ttyname) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "ttytype")) + { + if (opt.ttytype) + free (opt.ttytype); + opt.ttytype = strdup (value); + if (!opt.ttytype) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "lc-ctype")) + { + if (opt.lc_ctype) + free (opt.lc_ctype); + opt.lc_ctype = strdup (value); + if (!opt.lc_ctype) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "lc-messages")) + { + if (opt.lc_messages) + free (opt.lc_messages); + opt.lc_messages = strdup (value); + if (!opt.lc_messages) + return ASSUAN_Out_Of_Core; + } + else if (!strcmp (key, "list-mode")) + { + int i = *value? atoi (value) : 0; + if (!i || i == 1) /* default and mode 1 */ + { + ctrl->server_local->list_internal = 1; + ctrl->server_local->list_external = 0; + } + else if (i == 2) + { + ctrl->server_local->list_internal = 0; + ctrl->server_local->list_external = 1; + } + else if (i == 3) + { + ctrl->server_local->list_internal = 1; + ctrl->server_local->list_external = 1; + } + else + return ASSUAN_Parameter_Error; + } + else + return ASSUAN_Invalid_Option; + + return 0; +} + + + + +static void +reset_notify (ASSUAN_CONTEXT ctx) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + gpgsm_release_certlist (ctrl->server_local->recplist); + gpgsm_release_certlist (ctrl->server_local->signerlist); + ctrl->server_local->recplist = NULL; + ctrl->server_local->signerlist = NULL; + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); +} + + +static void +input_notify (ASSUAN_CONTEXT ctx, const char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + ctrl->autodetect_encoding = 0; + ctrl->is_pem = 0; + ctrl->is_base64 = 0; + if (strstr (line, "--armor")) + ctrl->is_pem = 1; + else if (strstr (line, "--base64")) + ctrl->is_base64 = 1; + else if (strstr (line, "--binary")) + ; + else + ctrl->autodetect_encoding = 1; +} + +static void +output_notify (ASSUAN_CONTEXT ctx, const char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + ctrl->create_pem = 0; + ctrl->create_base64 = 0; + if (strstr (line, "--armor")) + ctrl->create_pem = 1; + else if (strstr (line, "--base64")) + ctrl->create_base64 = 1; /* just the raw output */ +} + + + +/* RECIPIENT <userID> + + Set the recipient for the encryption. <userID> should be the + internal representation of the key; the server may accept any other + way of specification [we will support this]. If this is a valid and + trusted recipient the server does respond with OK, otherwise the + return is an ERR with the reason why the recipient can't be used, + the encryption will then not be done for this recipient. IF the + policy is not to encrypt at all if not all recipients are valid, the + client has to take care of this. All RECIPIENT commands are + cumulative until a RESET or an successful ENCRYPT command. */ +static int +cmd_recipient (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + + rc = gpgsm_add_to_certlist (ctrl, line, 0, &ctrl->server_local->recplist); + if (rc) + { + gpg_err_code_t r = gpg_err_code (rc); + gpgsm_status2 (ctrl, STATUS_INV_RECP, + r == -1? "1": + r == GPG_ERR_NO_PUBKEY? "1": + r == GPG_ERR_AMBIGUOUS_NAME? "2": + r == GPG_ERR_WRONG_KEY_USAGE? "3": + r == GPG_ERR_CERT_REVOKED? "4": + r == GPG_ERR_CERT_EXPIRED? "5": + r == GPG_ERR_NO_CRL_KNOWN? "6": + r == GPG_ERR_CRL_TOO_OLD? "7": + r == GPG_ERR_NO_POLICY_MATCH? "8": + "0", + line, NULL); + } + + return map_to_assuan_status (rc); +} + +/* SIGNER <userID> + + Set the signer's keys for the signature creation. <userID> should + be the internal representation of the key; the server may accept any + other way of specification [we will support this]. If this is a + valid and usable signing key the server does respond with OK, + otherwise it returns an ERR with the reason why the key can't be + used, the signing will then not be done for this key. If the policy + is not to sign at all if not all signer keys are valid, the client + has to take care of this. All SIGNER commands are cumulative until + a RESET but they are *not* reset by an SIGN command becuase it can + be expected that set of signers are used for more than one sign + operation. + + Note that this command returns an INV_RECP status which is a bit + strange, but they are very similar. */ +static int +cmd_signer (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + + rc = gpgsm_add_to_certlist (ctrl, line, 1, &ctrl->server_local->signerlist); + if (rc) + { + gpg_err_code_t r = gpg_err_code (rc); + gpgsm_status2 (ctrl, STATUS_INV_RECP, + r == -1? "1": + r == GPG_ERR_NO_PUBKEY? "1": + r == GPG_ERR_AMBIGUOUS_NAME? "2": + r == GPG_ERR_WRONG_KEY_USAGE? "3": + r == GPG_ERR_CERT_REVOKED? "4": + r == GPG_ERR_CERT_EXPIRED? "5": + r == GPG_ERR_NO_CRL_KNOWN? "6": + r == GPG_ERR_CRL_TOO_OLD? "7": + r == GPG_ERR_NO_POLICY_MATCH? "8": + r == GPG_ERR_NO_SECKEY? "9": + "0", + line, NULL); + } + return map_to_assuan_status (rc); +} + + +/* ENCRYPT + + Do the actual encryption process. Takes the plaintext from the INPUT + command, writes to the ciphertext to the file descriptor set with + the OUTPUT command, take the recipients form all the recipients set + so far. If this command fails the clients should try to delete all + output currently done or otherwise mark it as invalid. GPGSM does + ensure that there won't be any security problem with leftover data + on the output in this case. + + This command should in general not fail, as all necessary checks + have been done while setting the recipients. The input and output + pipes are closed. */ +static int +cmd_encrypt (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int rc; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == -1) + return set_error (No_Input, NULL); + out_fd = assuan_get_output_fd (ctx); + if (out_fd == -1) + return set_error (No_Output, NULL); + + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (General_Error, "fdopen() failed"); + rc = gpgsm_encrypt (assuan_get_pointer (ctx), + ctrl->server_local->recplist, + inp_fd, out_fp); + fclose (out_fp); + + gpgsm_release_certlist (ctrl->server_local->recplist); + ctrl->server_local->recplist = NULL; + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + return map_to_assuan_status (rc); +} + +/* DECRYPT + + This performs the decrypt operation after doing some check on the + internal state. (e.g. that only needed data has been set). Because + it utilizes the GPG-Agent for the session key decryption, there is + no need to ask the client for a protecting passphrase - GpgAgent + does take care of this by requesting this from the user. */ +static int +cmd_decrypt (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int rc; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == -1) + return set_error (No_Input, NULL); + out_fd = assuan_get_output_fd (ctx); + if (out_fd == -1) + return set_error (No_Output, NULL); + + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (General_Error, "fdopen() failed"); + rc = gpgsm_decrypt (ctrl, inp_fd, out_fp); + fclose (out_fp); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + +/* VERIFY + + This does a verify operation on the message send to the input-FD. + The result is written out using status lines. If an output FD was + given, the signed text will be written to that. + + If the signature is a detached one, the server will inquire about + the signed material and the client must provide it. + */ +static int +cmd_verify (ASSUAN_CONTEXT ctx, char *line) +{ + int rc; + CTRL ctrl = assuan_get_pointer (ctx); + int fd = assuan_get_input_fd (ctx); + int out_fd = assuan_get_output_fd (ctx); + FILE *out_fp = NULL; + + if (fd == -1) + return set_error (No_Input, NULL); + + if (out_fd != -1) + { + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (General_Error, "fdopen() failed"); + } + + rc = gpgsm_verify (assuan_get_pointer (ctx), fd, + ctrl->server_local->message_fd, out_fp); + if (out_fp) + fclose (out_fp); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + +/* SIGN [--detached] + + Sign the data set with the INPUT command and write it to the sink + set by OUTPUT. With "--detached" specified, a detached signature is + created (surprise). */ +static int +cmd_sign (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int detached; + int rc; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == -1) + return set_error (No_Input, NULL); + out_fd = assuan_get_output_fd (ctx); + if (out_fd == -1) + return set_error (No_Output, NULL); + + detached = has_option (line, "--detached"); + + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (General_Error, "fdopen() failed"); + + rc = gpgsm_sign (assuan_get_pointer (ctx), ctrl->server_local->signerlist, + inp_fd, detached, out_fp); + fclose (out_fp); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + +/* IMPORT + + Import the certificates read form the input-fd, return status + message for each imported one. The import checks the validity of + the certificate but not of the entire chain. It is possible to + import expired certificates. */ +static int +cmd_import (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + int fd = assuan_get_input_fd (ctx); + + if (fd == -1) + return set_error (No_Input, NULL); + + rc = gpgsm_import (assuan_get_pointer (ctx), fd); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + +static int +cmd_export (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int fd = assuan_get_output_fd (ctx); + FILE *out_fp; + char *p; + STRLIST list, sl; + + if (fd == -1) + return set_error (No_Output, NULL); + + /* break the line down into an STRLIST */ + 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) + { + free_strlist (list); + return ASSUAN_Out_Of_Core; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + out_fp = fdopen ( dup(fd), "w"); + if (!out_fp) + { + free_strlist (list); + return set_error (General_Error, "fdopen() failed"); + } + + gpgsm_export (ctrl, list, out_fp); + fclose (out_fp); + free_strlist (list); + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + return 0; +} + + +static int +cmd_delkeys (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + char *p; + STRLIST list, sl; + int rc; + + /* break the line down into an STRLIST */ + 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) + { + free_strlist (list); + return ASSUAN_Out_Of_Core; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + rc = gpgsm_delete (ctrl, list); + free_strlist (list); + + /* close and reset the fd */ + close_message_fd (ctrl); + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + + +/* MESSAGE FD=<n> + + Set the file descriptor to read a message which is used with + detached signatures */ +static int +cmd_message (ASSUAN_CONTEXT ctx, char *line) +{ + char *endp; + int fd; + CTRL ctrl = assuan_get_pointer (ctx); + + if (strncmp (line, "FD=", 3)) + return set_error (Syntax_Error, "FD=<n> expected"); + line += 3; + if (!digitp (line)) + return set_error (Syntax_Error, "number required"); + fd = strtoul (line, &endp, 10); + if (*endp) + return set_error (Syntax_Error, "garbage found"); + if (fd == -1) + return set_error (No_Input, NULL); + + ctrl->server_local->message_fd = fd; + return 0; +} + + +static int +do_listkeys (ASSUAN_CONTEXT ctx, char *line, int mode) +{ + CTRL ctrl = assuan_get_pointer (ctx); + FILE *fp = assuan_get_data_fp (ctx); + char *p; + STRLIST list, sl; + unsigned int listmode; + + if (!fp) + return set_error (General_Error, "no data stream"); + + /* break the line down into an STRLIST */ + 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) + { + free_strlist (list); + return ASSUAN_Out_Of_Core; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + ctrl->with_colons = 1; + listmode = mode; + if (ctrl->server_local->list_internal) + listmode |= (1<<6); + if (ctrl->server_local->list_external) + listmode |= (1<<7); + gpgsm_list_keys (assuan_get_pointer (ctx), list, fp, listmode); + free_strlist (list); + return 0; +} + +static int +cmd_listkeys (ASSUAN_CONTEXT ctx, char *line) +{ + return do_listkeys (ctx, line, 3); +} + +static int +cmd_listsecretkeys (ASSUAN_CONTEXT ctx, char *line) +{ + return do_listkeys (ctx, line, 2); +} + + +/* GENKEY + + Read the parameters in native format from the input fd and write a + certificate request to the output. + */ +static int +cmd_genkey (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int inp_fd, out_fd; + FILE *out_fp; + int rc; + + inp_fd = assuan_get_input_fd (ctx); + if (inp_fd == -1) + return set_error (No_Input, NULL); + out_fd = assuan_get_output_fd (ctx); + if (out_fd == -1) + return set_error (No_Output, NULL); + + out_fp = fdopen ( dup(out_fd), "w"); + if (!out_fp) + return set_error (General_Error, "fdopen() failed"); + rc = gpgsm_genkey (ctrl, inp_fd, out_fp); + fclose (out_fp); + + /* close and reset the fds */ + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + + return map_to_assuan_status (rc); +} + + + + + +/* Tell the assuan library about our commands */ +static int +register_commands (ASSUAN_CONTEXT ctx) +{ + static struct { + const char *name; + int (*handler)(ASSUAN_CONTEXT, char *line); + } table[] = { + { "RECIPIENT", cmd_recipient }, + { "SIGNER", cmd_signer }, + { "ENCRYPT", cmd_encrypt }, + { "DECRYPT", cmd_decrypt }, + { "VERIFY", cmd_verify }, + { "SIGN", cmd_sign }, + { "IMPORT", cmd_import }, + { "EXPORT", cmd_export }, + { "INPUT", NULL }, + { "OUTPUT", NULL }, + { "MESSAGE", cmd_message }, + { "LISTKEYS", cmd_listkeys }, + { "LISTSECRETKEYS",cmd_listsecretkeys }, + { "GENKEY", cmd_genkey }, + { "DELKEYS", cmd_delkeys }, + { NULL } + }; + int i, rc; + + for (i=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler); + if (rc) + return rc; + } + return 0; +} + +/* Startup the server */ +void +gpgsm_server (void) +{ + int rc; + int filedes[2]; + ASSUAN_CONTEXT ctx; + struct server_control_s ctrl; + + memset (&ctrl, 0, sizeof ctrl); + gpgsm_init_default_ctrl (&ctrl); + + /* For now we use a simple pipe based server so that we can work + from scripts. We will later add options to run as a daemon and + wait for requests on a Unix domain socket */ + filedes[0] = 0; + filedes[1] = 1; + rc = assuan_init_pipe_server (&ctx, filedes); + if (rc) + { + log_error ("failed to initialize the server: %s\n", + assuan_strerror(rc)); + gpgsm_exit (2); + } + rc = register_commands (ctx); + if (rc) + { + log_error ("failed to the register commands with Assuan: %s\n", + assuan_strerror(rc)); + gpgsm_exit (2); + } + assuan_set_hello_line (ctx, "GNU Privacy Guard's S/M server ready"); + + assuan_register_reset_notify (ctx, reset_notify); + assuan_register_input_notify (ctx, input_notify); + assuan_register_output_notify (ctx, output_notify); + assuan_register_option_handler (ctx, option_handler); + + assuan_set_pointer (ctx, &ctrl); + ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); + ctrl.server_local->assuan_ctx = ctx; + ctrl.server_local->message_fd = -1; + ctrl.server_local->list_internal = 1; + ctrl.server_local->list_external = 0; + + if (DBG_ASSUAN) + assuan_set_log_stream (ctx, log_get_stream ()); + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + { + break; + } + else if (rc) + { + log_info ("Assuan accept problem: %s\n", assuan_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + log_info ("Assuan processing failed: %s\n", assuan_strerror (rc)); + continue; + } + } + + gpgsm_release_certlist (ctrl.server_local->recplist); + ctrl.server_local->recplist = NULL; + gpgsm_release_certlist (ctrl.server_local->signerlist); + ctrl.server_local->signerlist = NULL; + + assuan_deinit_server (ctx); +} + + +static const char * +get_status_string ( int no ) +{ + const char *s; + + switch (no) + { + case STATUS_ENTER : s = "ENTER"; break; + case STATUS_LEAVE : s = "LEAVE"; break; + case STATUS_ABORT : s = "ABORT"; break; + case STATUS_GOODSIG: s = "GOODSIG"; break; + case STATUS_SIGEXPIRED: s = "SIGEXPIRED"; break; + case STATUS_KEYREVOKED: s = "KEYREVOKED"; break; + case STATUS_BADSIG : s = "BADSIG"; break; + case STATUS_ERRSIG : s = "ERRSIG"; break; + case STATUS_BADARMOR : s = "BADARMOR"; break; + case STATUS_RSA_OR_IDEA : s= "RSA_OR_IDEA"; break; + case STATUS_TRUST_UNDEFINED: s = "TRUST_UNDEFINED"; break; + case STATUS_TRUST_NEVER : s = "TRUST_NEVER"; break; + case STATUS_TRUST_MARGINAL : s = "TRUST_MARGINAL"; break; + case STATUS_TRUST_FULLY : s = "TRUST_FULLY"; break; + case STATUS_TRUST_ULTIMATE : s = "TRUST_ULTIMATE"; break; + case STATUS_GET_BOOL : s = "GET_BOOL"; break; + case STATUS_GET_LINE : s = "GET_LINE"; break; + case STATUS_GET_HIDDEN : s = "GET_HIDDEN"; break; + case STATUS_GOT_IT : s = "GOT_IT"; break; + case STATUS_SHM_INFO : s = "SHM_INFO"; break; + case STATUS_SHM_GET : s = "SHM_GET"; break; + case STATUS_SHM_GET_BOOL : s = "SHM_GET_BOOL"; break; + case STATUS_SHM_GET_HIDDEN : s = "SHM_GET_HIDDEN"; break; + case STATUS_NEED_PASSPHRASE: s = "NEED_PASSPHRASE"; break; + case STATUS_VALIDSIG : s = "VALIDSIG"; break; + case STATUS_SIG_ID : s = "SIG_ID"; break; + case STATUS_ENC_TO : s = "ENC_TO"; break; + case STATUS_NODATA : s = "NODATA"; break; + case STATUS_BAD_PASSPHRASE : s = "BAD_PASSPHRASE"; break; + case STATUS_NO_PUBKEY : s = "NO_PUBKEY"; break; + case STATUS_NO_SECKEY : s = "NO_SECKEY"; break; + case STATUS_NEED_PASSPHRASE_SYM: s = "NEED_PASSPHRASE_SYM"; break; + case STATUS_DECRYPTION_FAILED: s = "DECRYPTION_FAILED"; break; + case STATUS_DECRYPTION_OKAY: s = "DECRYPTION_OKAY"; break; + case STATUS_MISSING_PASSPHRASE: s = "MISSING_PASSPHRASE"; break; + case STATUS_GOOD_PASSPHRASE : s = "GOOD_PASSPHRASE"; break; + case STATUS_GOODMDC : s = "GOODMDC"; break; + case STATUS_BADMDC : s = "BADMDC"; break; + case STATUS_ERRMDC : s = "ERRMDC"; break; + case STATUS_IMPORTED : s = "IMPORTED"; break; + case STATUS_IMPORT_RES : s = "IMPORT_RES"; break; + case STATUS_FILE_START : s = "FILE_START"; break; + case STATUS_FILE_DONE : s = "FILE_DONE"; break; + case STATUS_FILE_ERROR : s = "FILE_ERROR"; break; + case STATUS_BEGIN_DECRYPTION:s = "BEGIN_DECRYPTION"; break; + case STATUS_END_DECRYPTION : s = "END_DECRYPTION"; break; + case STATUS_BEGIN_ENCRYPTION:s = "BEGIN_ENCRYPTION"; break; + case STATUS_END_ENCRYPTION : s = "END_ENCRYPTION"; break; + case STATUS_DELETE_PROBLEM : s = "DELETE_PROBLEM"; break; + case STATUS_PROGRESS : s = "PROGRESS"; break; + case STATUS_SIG_CREATED : s = "SIG_CREATED"; break; + case STATUS_SESSION_KEY : s = "SESSION_KEY"; break; + case STATUS_NOTATION_NAME : s = "NOTATION_NAME" ; break; + case STATUS_NOTATION_DATA : s = "NOTATION_DATA" ; break; + case STATUS_POLICY_URL : s = "POLICY_URL" ; break; + case STATUS_BEGIN_STREAM : s = "BEGIN_STREAM"; break; + case STATUS_END_STREAM : s = "END_STREAM"; break; + case STATUS_KEY_CREATED : s = "KEY_CREATED"; break; + case STATUS_UNEXPECTED : s = "UNEXPECTED"; break; + case STATUS_INV_RECP : s = "INV_RECP"; break; + case STATUS_NO_RECP : s = "NO_RECP"; break; + case STATUS_ALREADY_SIGNED : s = "ALREADY_SIGNED"; break; + case STATUS_EXPSIG : s = "EXPSIG"; break; + case STATUS_EXPKEYSIG : s = "EXPKEYSIG"; break; + case STATUS_TRUNCATED : s = "TRUNCATED"; break; + case STATUS_ERROR : s = "ERROR"; break; + case STATUS_IMPORT_PROBLEM : s = "IMPORT_PROBLEM"; break; + default: s = "?"; break; + } + return s; +} + + +void +gpgsm_status2 (CTRL ctrl, int no, ...) +{ + va_list arg_ptr; + const char *text; + + va_start (arg_ptr, no); + + if (ctrl->no_server) + { + if (ctrl->status_fd == -1) + return; /* no status wanted */ + if (!statusfp) + { + if (ctrl->status_fd == 1) + statusfp = stdout; + else if (ctrl->status_fd == 2) + statusfp = stderr; + else + statusfp = fdopen (ctrl->status_fd, "w"); + + if (!statusfp) + { + log_fatal ("can't open fd %d for status output: %s\n", + ctrl->status_fd, strerror(errno)); + } + } + + fputs ("[GNUPG:] ", statusfp); + fputs (get_status_string (no), statusfp); + + while ( (text = va_arg (arg_ptr, const char*) )) + { + putc ( ' ', statusfp ); + for (; *text; text++) + { + if (*text == '\n') + fputs ( "\\n", statusfp ); + else if (*text == '\r') + fputs ( "\\r", statusfp ); + else + putc ( *(const byte *)text, statusfp ); + } + } + putc ('\n', statusfp); + fflush (statusfp); + } + else + { + ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx; + char buf[950], *p; + size_t n; + + p = buf; + n = 0; + while ( (text = va_arg (arg_ptr, const char *)) ) + { + if (n) + { + *p++ = ' '; + n++; + } + for ( ; *text && n < DIM (buf)-2; n++) + *p++ = *text++; + } + *p = 0; + assuan_write_status (ctx, get_status_string (no), buf); + } + + va_end (arg_ptr); +} + +void +gpgsm_status (CTRL ctrl, int no, const char *text) +{ + gpgsm_status2 (ctrl, no, text, NULL); +} + +void +gpgsm_status_with_err_code (CTRL ctrl, int no, const char *text, + gpg_err_code_t ec) +{ + char buf[30]; + + sprintf (buf, "%u", (unsigned int)ec); + if (text) + gpgsm_status2 (ctrl, no, text, buf, NULL); + else + gpgsm_status2 (ctrl, no, buf, NULL); +} + +#if 0 +/* + * Write a status line with a buffer using %XX escapes. If WRAP is > + * 0 wrap the line after this length. If STRING is not NULL it will + * be prepended to the buffer, no escaping is done for string. + * A wrap of -1 forces spaces not to be encoded as %20. + */ +void +write_status_text_and_buffer ( int no, const char *string, + const char *buffer, size_t len, int wrap ) +{ + const char *s, *text; + int esc, first; + int lower_limit = ' '; + size_t n, count, dowrap; + + if( !statusfp ) + return; /* not enabled */ + + if (wrap == -1) { + lower_limit--; + wrap = 0; + } + + text = get_status_string (no); + count = dowrap = first = 1; + do { + if (dowrap) { + fprintf (statusfp, "[GNUPG:] %s ", text ); + count = dowrap = 0; + if (first && string) { + fputs (string, statusfp); + count += strlen (string); + } + first = 0; + } + for (esc=0, s=buffer, n=len; n && !esc; s++, n-- ) { + if ( *s == '%' || *(const byte*)s <= lower_limit + || *(const byte*)s == 127 ) + esc = 1; + if ( wrap && ++count > wrap ) { + dowrap=1; + break; + } + } + if (esc) { + s--; n++; + } + if (s != buffer) + fwrite (buffer, s-buffer, 1, statusfp ); + if ( esc ) { + fprintf (statusfp, "%%%02X", *(const byte*)s ); + s++; n--; + } + buffer = s; + len = n; + if ( dowrap && len ) + putc ( '\n', statusfp ); + } while ( len ); + + putc ('\n',statusfp); + fflush (statusfp); +} +#endif diff --git a/sm/sign.c b/sm/sign.c new file mode 100644 index 000000000..0afb52b62 --- /dev/null +++ b/sm/sign.c @@ -0,0 +1,621 @@ +/* sign.c - Sign a message + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + + +static void +hash_data (int fd, gcry_md_hd_t md) +{ + FILE *fp; + char buffer[4096]; + int nread; + + fp = fdopen ( dup (fd), "rb"); + if (!fp) + { + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + return; + } + + do + { + nread = fread (buffer, 1, DIM(buffer), fp); + gcry_md_write (md, buffer, nread); + } + while (nread); + if (ferror (fp)) + log_error ("read error on fd %d: %s\n", fd, strerror (errno)); + fclose (fp); +} + +static int +hash_and_copy_data (int fd, gcry_md_hd_t md, KsbaWriter writer) +{ + KsbaError err; + FILE *fp; + char buffer[4096]; + int nread; + int rc = 0; + int any = 0; + + fp = fdopen ( dup (fd), "rb"); + if (!fp) + { + gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + return tmperr; + } + + do + { + nread = fread (buffer, 1, DIM(buffer), fp); + if (nread) + { + any = 1; + gcry_md_write (md, buffer, nread); + err = ksba_writer_write_octet_string (writer, buffer, nread, 0); + if (err) + { + log_error ("write failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + } + } + } + while (nread && !rc); + if (ferror (fp)) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("read error on fd %d: %s\n", fd, strerror (errno)); + } + fclose (fp); + if (!any) + { + /* We can't allow to sign an empty message because it does not + make much sense and more seriously, ksba-cms_build has + already written the tag for data and now expects an octet + string but an octet string of zeize 0 is illegal. */ + log_error ("cannot sign an empty message\n"); + rc = gpg_error (GPG_ERR_NO_DATA); + } + if (!rc) + { + err = ksba_writer_write_octet_string (writer, NULL, 0, 1); + if (err) + { + log_error ("write failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + } + } + + return rc; +} + + +/* Get the default certificate which is defined as the first one our + keyDB retruns and has a secret key available */ +int +gpgsm_get_default_cert (KsbaCert *r_cert) +{ + KEYDB_HANDLE hd; + KsbaCert cert = NULL; + int rc; + char *p; + + hd = keydb_new (0); + if (!hd) + return gpg_error (GPG_ERR_GENERAL); + rc = keydb_search_first (hd); + if (rc) + { + keydb_release (hd); + return rc; + } + + do + { + rc = keydb_get_cert (hd, &cert); + if (rc) + { + log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc)); + keydb_release (hd); + return rc; + } + + p = gpgsm_get_keygrip_hexstring (cert); + if (p) + { + if (!gpgsm_agent_havekey (p)) + { + xfree (p); + keydb_release (hd); + *r_cert = cert; + return 0; /* got it */ + } + xfree (p); + } + + ksba_cert_release (cert); + cert = NULL; + } + while (!(rc = keydb_search_next (hd))); + if (rc && rc != -1) + log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc)); + + ksba_cert_release (cert); + keydb_release (hd); + return rc; +} + + +static KsbaCert +get_default_signer (void) +{ + KEYDB_SEARCH_DESC desc; + KsbaCert cert = NULL; + KEYDB_HANDLE kh = NULL; + int rc; + + if (!opt.local_user) + { + rc = gpgsm_get_default_cert (&cert); + if (rc) + { + if (rc != -1) + log_debug ("failed to find default certificate: %s\n", + gpg_strerror (rc)); + return NULL; + } + return cert; + } + + rc = keydb_classify_name (opt.local_user, &desc); + if (rc) + { + log_error ("failed to find default signer: %s\n", gpg_strerror (rc)); + return NULL; + } + + kh = keydb_new (0); + if (!kh) + return NULL; + + rc = keydb_search (kh, &desc, 1); + if (rc) + { + log_debug ("failed to find default certificate: rc=%d\n", rc); + } + else + { + rc = keydb_get_cert (kh, &cert); + if (rc) + { + log_debug ("failed to get cert: rc=%d\n", rc); + } + } + + keydb_release (kh); + return cert; +} + +/* Depending on the options in CTRL add the certificate CERT as well as + other certificate up in the chain to the Root-CA to the CMS + object. */ +static int +add_certificate_list (CTRL ctrl, KsbaCMS cms, KsbaCert cert) +{ + KsbaError err; + int rc = 0; + KsbaCert next = NULL; + int n; + int not_root = 0; + + ksba_cert_ref (cert); + + n = ctrl->include_certs; + if (n == -2) + { + not_root = 1; + n = -1; + } + if (n < 0 || n > 50) + n = 50; /* We better apply an upper bound */ + + if (n) + { + if (not_root && gpgsm_is_root_cert (cert)) + err = 0; + else + err = ksba_cms_add_cert (cms, cert); + if (err) + goto ksba_failure; + } + while ( n-- && !(rc = gpgsm_walk_cert_chain (cert, &next)) ) + { + if (not_root && gpgsm_is_root_cert (next)) + err = 0; + else + err = ksba_cms_add_cert (cms, next); + ksba_cert_release (cert); + cert = next; next = NULL; + if (err) + goto ksba_failure; + } + ksba_cert_release (cert); + + return rc == -1? 0: rc; + + ksba_failure: + ksba_cert_release (cert); + log_error ("ksba_cms_add_cert failed: %s\n", ksba_strerror (err)); + return map_ksba_err (err); +} + + + + +/* Perform a sign operation. + + Sign the data received on DATA-FD in embedded mode or in detached + mode when DETACHED is true. Write the signature to OUT_FP. The + keys used to sign are taken from SIGNERLIST or the default one will + be used if the value of this argument is NULL. */ +int +gpgsm_sign (CTRL ctrl, CERTLIST signerlist, + int data_fd, int detached, FILE *out_fp) +{ + int i, rc; + KsbaError err; + Base64Context b64writer = NULL; + KsbaWriter writer; + KsbaCMS cms = NULL; + KsbaStopReason stopreason; + KEYDB_HANDLE kh = NULL; + gcry_md_hd_t data_md = NULL; + int signer; + const char *algoid; + int algo; + time_t signed_at; + CERTLIST cl; + int release_signerlist = 0; + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + ctrl->pem_name = "SIGNED MESSAGE"; + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + + cms = ksba_cms_new (); + if (!cms) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + err = ksba_cms_set_reader_writer (cms, NULL, writer); + if (err) + { + log_debug ("ksba_cms_set_reader_writer failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + /* We are going to create signed data with data as encap. content */ + err = ksba_cms_set_content_type (cms, 0, KSBA_CT_SIGNED_DATA); + if (!err) + err = ksba_cms_set_content_type (cms, 1, KSBA_CT_DATA); + if (err) + { + log_debug ("ksba_cms_set_content_type failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + /* If no list of signers is given, use a default one. */ + if (!signerlist) + { + KsbaCert cert = get_default_signer (); + if (!cert) + { + log_error ("no default signer found\n"); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + signerlist = xtrycalloc (1, sizeof *signerlist); + if (!signerlist) + { + rc = OUT_OF_CORE (errno); + ksba_cert_release (cert); + goto leave; + } + signerlist->cert = cert; + release_signerlist = 1; + } + + + /* Gather certificates of signers and store them in the CMS object. */ + for (cl=signerlist; cl; cl = cl->next) + { + rc = gpgsm_cert_use_sign_p (cl->cert); + if (rc) + goto leave; + + err = ksba_cms_add_signer (cms, cl->cert); + if (err) + { + log_error ("ksba_cms_add_signer failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + rc = add_certificate_list (ctrl, cms, cl->cert); + if (rc) + { + log_error ("failed to store list of certificates: %s\n", + gpg_strerror(rc)); + goto leave; + } + /* Set the hash algorithm we are going to use */ + err = ksba_cms_add_digest_algo (cms, "1.3.14.3.2.26" /*SHA-1*/); + if (err) + { + log_debug ("ksba_cms_add_digest_algo failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + + /* Prepare hashing (actually we are figuring out what we have set above)*/ + rc = gcry_md_open (&data_md, 0, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (data_md, "sign.data"); + + for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) + { + algo = gcry_md_map_name (algoid); + if (!algo) + { + log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + gcry_md_enable (data_md, algo); + } + + if (detached) + { /* we hash the data right now so that we can store the message + digest. ksba_cms_build() takes this as an flag that detached + data is expected. */ + unsigned char *digest; + size_t digest_len; + /* Fixme do this for all signers and get the algo to use from + the signer's certificate - does not make mich sense, bu we + should do this consistent as we have already done it above */ + algo = GCRY_MD_SHA1; + hash_data (data_fd, data_md); + digest = gcry_md_read (data_md, algo); + digest_len = gcry_md_get_algo_dlen (algo); + if ( !digest || !digest_len) + { + log_error ("problem getting the hash of the data\n"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + err = ksba_cms_set_message_digest (cms, signer, digest, digest_len); + if (err) + { + log_error ("ksba_cms_set_message_digest failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + } + + signed_at = gnupg_get_time (); + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + err = ksba_cms_set_signing_time (cms, signer, signed_at); + if (err) + { + log_error ("ksba_cms_set_signing_time failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + + do + { + err = ksba_cms_build (cms, &stopreason); + if (err) + { + log_debug ("ksba_cms_build failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + if (stopreason == KSBA_SR_BEGIN_DATA) + { /* hash the data and store the message digest */ + unsigned char *digest; + size_t digest_len; + + assert (!detached); + /* Fixme: get the algo to use from the signer's certificate + - does not make much sense, but we should do this + consistent as we have already done it above. Code is + mostly duplicated above. */ + + algo = GCRY_MD_SHA1; + rc = hash_and_copy_data (data_fd, data_md, writer); + if (rc) + goto leave; + digest = gcry_md_read (data_md, algo); + digest_len = gcry_md_get_algo_dlen (algo); + if ( !digest || !digest_len) + { + log_error ("problem getting the hash of the data\n"); + rc = gpg_error (GPG_ERR_BUG); + goto leave; + } + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + err = ksba_cms_set_message_digest (cms, signer, + digest, digest_len); + if (err) + { + log_error ("ksba_cms_set_message_digest failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + } + } + else if (stopreason == KSBA_SR_NEED_SIG) + { /* calculate the signature for all signers */ + gcry_md_hd_t md; + + algo = GCRY_MD_SHA1; + rc = gcry_md_open (&md, algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "sign.attr"); + ksba_cms_set_hash_function (cms, HASH_FNC, md); + for (cl=signerlist,signer=0; cl; cl = cl->next, signer++) + { + char *sigval = NULL; + char *buf, *fpr; + + if (signer) + gcry_md_reset (md); + rc = ksba_cms_hash_signed_attrs (cms, signer); + if (rc) + { + log_debug ("hashing signed attrs failed: %s\n", + ksba_strerror (rc)); + gcry_md_close (md); + goto leave; + } + + rc = gpgsm_create_cms_signature (cl->cert, md, algo, &sigval); + if (rc) + { + gcry_md_close (md); + goto leave; + } + + err = ksba_cms_set_sig_val (cms, signer, sigval); + xfree (sigval); + if (err) + { + log_error ("failed to store the signature: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + gcry_md_close (md); + goto leave; + } + + /* write a status message */ + fpr = gpgsm_get_fingerprint_hexstring (cl->cert, GCRY_MD_SHA1); + if (!fpr) + { + rc = gpg_error (GPG_ERR_ENOMEM); + gcry_md_close (md); + goto leave; + } + rc = asprintf (&buf, "%c %d %d 00 %lu %s", + detached? 'D':'S', + GCRY_PK_RSA, /* FIXME: get pk algo from cert */ + algo, + (ulong)signed_at, + fpr); + xfree (fpr); + if (rc < 0) + { + rc = gpg_error (GPG_ERR_ENOMEM); + gcry_md_close (md); + goto leave; + } + rc = 0; + gpgsm_status (ctrl, STATUS_SIG_CREATED, buf); + free (buf); /* yes, we must use the regular free() here */ + } + gcry_md_close (md); + + } + } + while (stopreason != KSBA_SR_READY); + + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + log_info ("signature created\n"); + + + leave: + if (release_signerlist) + gpgsm_release_certlist (signerlist); + ksba_cms_release (cms); + gpgsm_destroy_writer (b64writer); + keydb_release (kh); + gcry_md_close (data_md); + return rc; +} diff --git a/sm/verify.c b/sm/verify.c new file mode 100644 index 000000000..6dd4f4e5b --- /dev/null +++ b/sm/verify.c @@ -0,0 +1,550 @@ +/* verify.c - Verify a messages signature + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> +#include <assert.h> + +#include "gpgsm.h" +#include <gcrypt.h> +#include <ksba.h> + +#include "keydb.h" +#include "i18n.h" + +static char * +strtimestamp_r (time_t atime) +{ + char *buffer = xmalloc (15); + + if (atime < 0) + strcpy (buffer, "????" "-??" "-??"); + else if (!atime) + strcpy (buffer, "none"); + else + { + struct tm *tp; + + tp = gmtime( &atime ); + sprintf (buffer, "%04d-%02d-%02d", + 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday); + } + return buffer; +} + + + +/* Hash the data for a detached signature */ +static void +hash_data (int fd, gcry_md_hd_t md) +{ + FILE *fp; + char buffer[4096]; + int nread; + + fp = fdopen ( dup (fd), "rb"); + if (!fp) + { + log_error ("fdopen(%d) failed: %s\n", fd, strerror (errno)); + return; + } + + do + { + nread = fread (buffer, 1, DIM(buffer), fp); + gcry_md_write (md, buffer, nread); + } + while (nread); + if (ferror (fp)) + log_error ("read error on fd %d: %s\n", fd, strerror (errno)); + fclose (fp); +} + + + + +/* Perform a verify operation. To verify detached signatures, data_fd + must be different than -1. With OUT_FP given and a non-detached + signature, the signed material is written to that stream. */ +int +gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp) +{ + int i, rc; + Base64Context b64reader = NULL; + Base64Context b64writer = NULL; + KsbaError err; + KsbaReader reader; + KsbaWriter writer = NULL; + KsbaCMS cms = NULL; + KsbaStopReason stopreason; + KsbaCert cert; + KEYDB_HANDLE kh; + gcry_md_hd_t data_md = NULL; + int signer; + const char *algoid; + int algo; + int is_detached; + FILE *fp = NULL; + char *p; + + kh = keydb_new (0); + if (!kh) + { + log_error (_("failed to allocated keyDB handle\n")); + rc = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + + fp = fdopen ( dup (in_fd), "rb"); + if (!fp) + { + rc = gpg_error (gpg_err_code_from_errno (errno)); + log_error ("fdopen() failed: %s\n", strerror (errno)); + goto leave; + } + + rc = gpgsm_create_reader (&b64reader, ctrl, fp, &reader); + if (rc) + { + log_error ("can't create reader: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (out_fp) + { + rc = gpgsm_create_writer (&b64writer, ctrl, out_fp, &writer); + if (rc) + { + log_error ("can't create writer: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + cms = ksba_cms_new (); + if (!cms) + { + rc = gpg_error (GPG_ERR_ENOMEM); + goto leave; + } + + err = ksba_cms_set_reader_writer (cms, reader, writer); + if (err) + { + log_error ("ksba_cms_set_reader_writer failed: %s\n", + ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + rc = gcry_md_open (&data_md, 0, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto leave; + } + if (DBG_HASHING) + gcry_md_start_debug (data_md, "vrfy.data"); + + is_detached = 0; + do + { + err = ksba_cms_parse (cms, &stopreason); + if (err) + { + log_error ("ksba_cms_parse failed: %s\n", ksba_strerror (err)); + rc = map_ksba_err (err); + goto leave; + } + + if (stopreason == KSBA_SR_NEED_HASH) + { + is_detached = 1; + if (opt.verbose) + log_info ("detached signature\n"); + } + + if (stopreason == KSBA_SR_NEED_HASH + || stopreason == KSBA_SR_BEGIN_DATA) + { /* We are now able to enable the hash algorithms */ + for (i=0; (algoid=ksba_cms_get_digest_algo_list (cms, i)); i++) + { + algo = gcry_md_map_name (algoid); + if (!algo) + log_error ("unknown hash algorithm `%s'\n", + algoid? algoid:"?"); + else + gcry_md_enable (data_md, algo); + } + if (is_detached) + { + if (data_fd == -1) + log_info ("detached signature w/o data " + "- assuming certs-only\n"); + else + hash_data (data_fd, data_md); + } + else + { + ksba_cms_set_hash_function (cms, HASH_FNC, data_md); + } + } + else if (stopreason == KSBA_SR_END_DATA) + { /* The data bas been hashed */ + + } + } + while (stopreason != KSBA_SR_READY); + + if (b64writer) + { + rc = gpgsm_finish_writer (b64writer); + if (rc) + { + log_error ("write failed: %s\n", gpg_strerror (rc)); + goto leave; + } + } + + if (data_fd != -1 && !is_detached) + { + log_error ("data given for a non-detached signature\n"); + rc = gpg_error (GPG_ERR_CONFLICT); + goto leave; + } + + for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++) + { + /* Fixme: it might be better to check the validity of the + certificate first before entering it into the DB. This way + we would avoid cluttering the DB with invalid + certificates. */ + keydb_store_cert (cert, 0, NULL); + ksba_cert_release (cert); + } + + cert = NULL; + err = 0; + for (signer=0; ; signer++) + { + char *issuer = NULL; + KsbaSexp sigval = NULL; + time_t sigtime, keyexptime; + KsbaSexp serial; + char *msgdigest = NULL; + size_t msgdigestlen; + char *ctattr; + + err = ksba_cms_get_issuer_serial (cms, signer, &issuer, &serial); + if (!signer && err == KSBA_No_Data && data_fd == -1 && is_detached) + { + log_info ("certs-only message accepted\n"); + err = 0; + break; + } + if (err) + { + if (signer && err == -1) + err = 0; + break; + } + if (DBG_X509) + { + log_debug ("signer %d - issuer: `%s'\n", + signer, issuer? issuer:"[NONE]"); + log_debug ("signer %d - serial: ", signer); + gpgsm_dump_serial (serial); + log_printf ("\n"); + } + + err = ksba_cms_get_signing_time (cms, signer, &sigtime); + if (err == KSBA_No_Data) + sigtime = 0; + else if (err) + { + log_error ("error getting signing time: %s\n", ksba_strerror (err)); + sigtime = (time_t)-1; + } + + err = ksba_cms_get_message_digest (cms, signer, + &msgdigest, &msgdigestlen); + if (!err) + { + algoid = ksba_cms_get_digest_algo (cms, signer); + algo = gcry_md_map_name (algoid); + if (DBG_X509) + log_debug ("signer %d - digest algo: %d\n", signer, algo); + if ( !gcry_md_info (data_md, GCRYCTL_IS_ALGO_ENABLED, &algo, NULL) ) + { + log_error ("digest algo %d has not been enabled\n", algo); + goto next_signer; + } + } + else if (err == KSBA_No_Data) + { + assert (!msgdigest); + err = 0; + algoid = NULL; + algo = 0; + } + else /* real error */ + break; + + err = ksba_cms_get_sigattr_oids (cms, signer, + "1.2.840.113549.1.9.3",&ctattr); + if (!err) + { + const char *s; + + if (DBG_X509) + log_debug ("signer %d - content-type attribute: %s", signer, ctattr); + s = ksba_cms_get_content_oid (cms, 1); + if (!s || strcmp (ctattr, s)) + { + log_error ("content-type attribute does not match " + "actual content-type\n"); + ksba_free (ctattr); + ctattr = NULL; + goto next_signer; + } + ksba_free (ctattr); + ctattr = NULL; + } + else if (err != -1) + { + log_error ("error getting content-type attribute: %s\n", + ksba_strerror (err)); + goto next_signer; + } + err = 0; + + + sigval = ksba_cms_get_sig_val (cms, signer); + if (!sigval) + { + log_error ("no signature value available\n"); + goto next_signer; + } + if (DBG_X509) + log_debug ("signer %d - signature available", signer); + + /* Find the certificate of the signer */ + keydb_search_reset (kh); + rc = keydb_search_issuer_sn (kh, issuer, serial); + if (rc) + { + if (rc == -1) + { + log_error ("certificate not found\n"); + rc = gpg_error (GPG_ERR_NO_PUBKEY); + } + else + log_error ("failed to find the certificate: %s\n", + gpg_strerror(rc)); + { + char numbuf[50]; + sprintf (numbuf, "%d", rc); + + gpgsm_status2 (ctrl, STATUS_ERROR, "verify.findkey", + numbuf, NULL); + } + /* fixme: we might want to append the issuer and serial + using our standard notation */ + goto next_signer; + } + + rc = keydb_get_cert (kh, &cert); + if (rc) + { + log_error ("failed to get cert: %s\n", gpg_strerror (rc)); + goto next_signer; + } + + log_info (_("Signature made ")); + if (sigtime) + gpgsm_dump_time (sigtime); + else + log_printf (_("[date not given]")); + log_printf (_(" using certificate ID %08lX\n"), + gpgsm_get_short_fingerprint (cert)); + + + if (msgdigest) + { /* Signed attributes are available. */ + gcry_md_hd_t md; + unsigned char *s; + + /* check that the message digest in the signed attributes + matches the one we calculated on the data */ + s = gcry_md_read (data_md, algo); + if ( !s || !msgdigestlen + || gcry_md_get_algo_dlen (algo) != msgdigestlen + || !s || memcmp (s, msgdigest, msgdigestlen) ) + { + char *fpr; + + log_error ("invalid signature: message digest attribute " + "does not match calculated one\n"); + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + gpgsm_status (ctrl, STATUS_BADSIG, fpr); + xfree (fpr); + goto next_signer; + } + + rc = gcry_md_open (&md, algo, 0); + if (rc) + { + log_error ("md_open failed: %s\n", gpg_strerror (rc)); + goto next_signer; + } + if (DBG_HASHING) + gcry_md_start_debug (md, "vrfy.attr"); + + ksba_cms_set_hash_function (cms, HASH_FNC, md); + rc = ksba_cms_hash_signed_attrs (cms, signer); + if (rc) + { + log_error ("hashing signed attrs failed: %s\n", + ksba_strerror (rc)); + gcry_md_close (md); + goto next_signer; + } + rc = gpgsm_check_cms_signature (cert, sigval, md, algo); + gcry_md_close (md); + } + else + { + rc = gpgsm_check_cms_signature (cert, sigval, data_md, algo); + } + + if (rc) + { + char *fpr; + + log_error ("invalid signature: %s\n", gpg_strerror (rc)); + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + gpgsm_status (ctrl, STATUS_BADSIG, fpr); + xfree (fpr); + goto next_signer; + } + rc = gpgsm_cert_use_verify_p (cert); /*(this displays an info message)*/ + if (rc) + { + gpgsm_status_with_err_code (ctrl, STATUS_ERROR, "verify.keyusage", + gpg_err_code (rc)); + rc = 0; + } + + if (DBG_X509) + log_debug ("signature okay - checking certs\n"); + rc = gpgsm_validate_chain (ctrl, cert, &keyexptime); + if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED) + { + gpgsm_status (ctrl, STATUS_EXPKEYSIG, NULL); + rc = 0; + } + else + gpgsm_status (ctrl, STATUS_GOODSIG, NULL); + + { + char *buf, *fpr, *tstr; + + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); + tstr = strtimestamp_r (sigtime); + buf = xmalloc ( strlen(fpr) + strlen (tstr) + 120); + sprintf (buf, "%s %s %lu %lu", fpr, tstr, + (unsigned long)sigtime, (unsigned long)keyexptime ); + xfree (tstr); + xfree (fpr); + gpgsm_status (ctrl, STATUS_VALIDSIG, buf); + xfree (buf); + } + + if (rc) /* of validate_chain */ + { + log_error ("invalid certification chain: %s\n", gpg_strerror (rc)); + if (gpg_err_code (rc) == GPG_ERR_BAD_CERT_CHAIN + || gpg_err_code (rc) == GPG_ERR_BAD_CERT + || gpg_err_code (rc) == GPG_ERR_BAD_CA_CERT + || gpg_err_code (rc) == GPG_ERR_CERT_REVOKED) + gpgsm_status_with_err_code (ctrl, STATUS_TRUST_NEVER, NULL, + gpg_err_code (rc)); + else + gpgsm_status_with_err_code (ctrl, STATUS_TRUST_UNDEFINED, NULL, + gpg_err_code (rc)); + goto next_signer; + } + + for (i=0; (p = ksba_cert_get_subject (cert, i)); i++) + { + log_info (!i? _("Good signature from") + : _(" aka")); + log_printf (" \""); + gpgsm_print_name (log_get_stream (), p); + log_printf ("\"\n"); + ksba_free (p); + } + + gpgsm_status (ctrl, STATUS_TRUST_FULLY, NULL); + + + next_signer: + rc = 0; + xfree (issuer); + xfree (serial); + xfree (sigval); + xfree (msgdigest); + ksba_cert_release (cert); + cert = NULL; + } + rc = 0; + if (err) + { + log_error ("ksba error: %s\n", ksba_strerror (err)); + rc = map_ksba_err (rc); + } + + + + leave: + ksba_cms_release (cms); + gpgsm_destroy_reader (b64reader); + gpgsm_destroy_writer (b64writer); + keydb_release (kh); + gcry_md_close (data_md); + if (fp) + fclose (fp); + + if (rc) + { + char numbuf[50]; + sprintf (numbuf, "%d", rc ); + gpgsm_status2 (ctrl, STATUS_ERROR, "verify.leave", + numbuf, NULL); + } + + return rc; +} + |